]> git.basschouten.com Git - openhab-addons.git/blob
5b7593f823ab79008149a835cf5b30b667850dfa
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2022 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 Optional<Double> deadManSwitchTimeoutMs;
66     private final Item dutyCycleItem;
67     private @Nullable ServiceRegistration<?> eventSubscriberRegistration;
68     private @Nullable ScheduledFuture<?> deadMeanSwitchTimer;
69     private @Nullable StateMachine stateMachine;
70
71     public PWMTriggerHandler(Trigger module, ItemRegistry itemRegistry, BundleContext bundleContext) {
72         super(module);
73         this.bundleContext = bundleContext;
74
75         Configuration config = module.getConfiguration();
76
77         String dutycycleItemName = (String) Objects.requireNonNull(config.get(CONFIG_DUTY_CYCLE_ITEM),
78                 "DutyCycle item is not set");
79
80         minDutyCycle = getOptionalDoubleFromConfig(config, CONFIG_MIN_DUTYCYCLE);
81         maxDutyCycle = getOptionalDoubleFromConfig(config, CONFIG_MAX_DUTYCYCLE);
82         deadManSwitchTimeoutMs = getOptionalDoubleFromConfig(config, CONFIG_DEAD_MAN_SWITCH);
83
84         try {
85             dutyCycleItem = itemRegistry.getItem(dutycycleItemName);
86         } catch (ItemNotFoundException e) {
87             throw new IllegalArgumentException("Dutycycle item not found: " + dutycycleItemName, e);
88         }
89
90         eventFilter = event -> event.getTopic().equals("openhab/items/" + dutycycleItemName + "/state");
91     }
92
93     @Override
94     public void setCallback(ModuleHandlerCallback callback) {
95         super.setCallback(callback);
96
97         double periodSec = getDoubleFromConfig(module.getConfiguration(), CONFIG_PERIOD);
98         stateMachine = new StateMachine(getCallback().getScheduler(), this::setOutput, (long) (periodSec * 1000));
99
100         eventSubscriberRegistration = bundleContext.registerService(EventSubscriber.class.getName(), this, null);
101     }
102
103     private double getDoubleFromConfig(Configuration config, String key) {
104         return ((BigDecimal) Objects.requireNonNull(config.get(key), key + " is not set")).doubleValue();
105     }
106
107     private Optional<Double> getOptionalDoubleFromConfig(Configuration config, String key) {
108         Object o = config.get(key);
109
110         if (o instanceof BigDecimal) {
111             return Optional.of(((BigDecimal) o).doubleValue());
112         }
113
114         return Optional.empty();
115     }
116
117     @Override
118     public void receive(Event event) {
119         if (!(event instanceof ItemStateEvent)) {
120             return;
121         }
122
123         ItemStateEvent changedEvent = (ItemStateEvent) event;
124         synchronized (this) {
125             try {
126                 double newDutycycle = getDutyCycleValueInPercent(changedEvent.getItemState());
127                 double newDutycycleBeforeLimit = newDutycycle;
128
129                 restartDeadManSwitchTimer();
130
131                 // set duty cycle to min duty cycle if it is smaller than min duty cycle
132                 // set duty cycle to 0% if it is 0%, regardless of the min duty cycle
133                 final double newDutyCycleFinal1 = newDutycycle;
134                 newDutycycle = minDutyCycle.map(minDutycycle -> {
135                     if (Math.round(newDutyCycleFinal1) <= 0) {
136                         return 0d;
137                     } else {
138                         return Math.max(minDutycycle, newDutyCycleFinal1);
139                     }
140                 }).orElse(newDutycycle);
141
142                 // set duty cycle to 100% if the current duty cycle is larger than the max duty cycle
143                 final double newDutyCycleFinal2 = newDutycycle;
144                 newDutycycle = maxDutyCycle.map(maxDutycycle -> {
145                     if (Math.round(newDutyCycleFinal2) >= maxDutycycle) {
146                         return 100d;
147                     } else {
148                         return newDutyCycleFinal2;
149                     }
150                 }).orElse(newDutycycle);
151
152                 logger.debug("Received new duty cycle: {} {}", newDutycycleBeforeLimit,
153                         newDutycycle != newDutycycleBeforeLimit ? "Limited to: " + newDutycycle : "");
154
155                 StateMachine localStateMachine = stateMachine;
156                 if (localStateMachine != null) {
157                     localStateMachine.setDutycycle(newDutycycle);
158                 } else {
159                     logger.debug("Initialization not finished");
160                 }
161             } catch (PWMException e) {
162                 logger.warn("{}", e.getMessage());
163             }
164         }
165     }
166
167     private void restartDeadManSwitchTimer() {
168         ScheduledFuture<?> timer = deadMeanSwitchTimer;
169         if (timer != null) {
170             timer.cancel(true);
171         }
172
173         deadManSwitchTimeoutMs.ifPresent(timeout -> {
174             deadMeanSwitchTimer = getCallback().getScheduler().schedule(this::activateDeadManSwitch,
175                     timeout.longValue(), TimeUnit.MILLISECONDS);
176         });
177     }
178
179     private void activateDeadManSwitch() {
180         logger.warn("Dead-man switch activated. Disabling output");
181
182         StateMachine localStateMachine = stateMachine;
183         if (localStateMachine != null) {
184             localStateMachine.stop();
185         }
186     }
187
188     private void setOutput(boolean enable) {
189         getCallback().triggered(module, Collections.singletonMap(OUTPUT, OnOffType.from(enable)));
190     }
191
192     private TriggerHandlerCallback getCallback() {
193         ModuleHandlerCallback localCallback = callback;
194         if (localCallback != null && localCallback instanceof TriggerHandlerCallback) {
195             return (TriggerHandlerCallback) localCallback;
196         }
197
198         throw new IllegalStateException();
199     }
200
201     private double getDutyCycleValueInPercent(State state) throws PWMException {
202         if (state instanceof DecimalType) {
203             return ((DecimalType) state).doubleValue();
204         } else if (state instanceof StringType) {
205             try {
206                 return Integer.parseInt(state.toString());
207             } catch (NumberFormatException e) {
208                 // nothing
209             }
210         } else if (state instanceof UnDefType) {
211             throw new PWMException("Duty cycle item '" + dutyCycleItem.getName() + "' has no valid value");
212         }
213         throw new PWMException("Duty cycle item not of type DecimalType: " + state.getClass().getSimpleName());
214     }
215
216     @Override
217     public Set<String> getSubscribedEventTypes() {
218         return SUBSCRIBED_EVENT_TYPES;
219     }
220
221     @Override
222     public @Nullable EventFilter getEventFilter() {
223         return eventFilter;
224     }
225
226     @Override
227     public void dispose() {
228         ServiceRegistration<?> localEventSubscriberRegistration = eventSubscriberRegistration;
229         if (localEventSubscriberRegistration != null) {
230             localEventSubscriberRegistration.unregister();
231         }
232
233         StateMachine localStateMachine = stateMachine;
234         if (localStateMachine != null) {
235             localStateMachine.stop();
236         }
237
238         super.dispose();
239     }
240 }