2 * Copyright (c) 2010-2021 Contributors to the openHAB project
4 * See the NOTICE file(s) distributed with this work for additional
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
11 * SPDX-License-Identifier: EPL-2.0
13 package org.openhab.automation.pwm.internal.handler;
15 import static org.openhab.automation.pwm.internal.PWMConstants.*;
17 import java.math.BigDecimal;
18 import java.util.Collections;
19 import java.util.Objects;
20 import java.util.Optional;
22 import java.util.concurrent.ScheduledFuture;
23 import java.util.concurrent.TimeUnit;
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;
52 * Represents a Trigger module in the rules engine.
54 * @author Fabian Wolter - Initial Contribution
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;
71 public PWMTriggerHandler(Trigger module, ItemRegistry itemRegistry, BundleContext bundleContext) {
73 this.bundleContext = bundleContext;
75 Configuration config = module.getConfiguration();
77 String dutycycleItemName = (String) Objects.requireNonNull(config.get(CONFIG_DUTY_CYCLE_ITEM),
78 "DutyCycle item is not set");
80 minDutyCycle = getOptionalDoubleFromConfig(config, CONFIG_MIN_DUTYCYCLE);
81 maxDutyCycle = getOptionalDoubleFromConfig(config, CONFIG_MAX_DUTYCYCLE);
82 deadManSwitchTimeoutMs = getOptionalDoubleFromConfig(config, CONFIG_DEAD_MAN_SWITCH);
85 dutyCycleItem = itemRegistry.getItem(dutycycleItemName);
86 } catch (ItemNotFoundException e) {
87 throw new IllegalArgumentException("Dutycycle item not found: " + dutycycleItemName, e);
90 eventFilter = event -> event.getTopic().equals("openhab/items/" + dutycycleItemName + "/state");
94 public void setCallback(ModuleHandlerCallback callback) {
95 super.setCallback(callback);
97 double periodSec = getDoubleFromConfig(module.getConfiguration(), CONFIG_PERIOD);
98 stateMachine = new StateMachine(getCallback().getScheduler(), this::setOutput, (long) (periodSec * 1000));
100 eventSubscriberRegistration = bundleContext.registerService(EventSubscriber.class.getName(), this, null);
103 private double getDoubleFromConfig(Configuration config, String key) {
104 return ((BigDecimal) Objects.requireNonNull(config.get(key), key + " is not set")).doubleValue();
107 private Optional<Double> getOptionalDoubleFromConfig(Configuration config, String key) {
108 Object o = config.get(key);
110 if (o instanceof BigDecimal) {
111 return Optional.of(((BigDecimal) o).doubleValue());
114 return Optional.empty();
118 public void receive(Event event) {
119 if (!(event instanceof ItemStateEvent)) {
123 ItemStateEvent changedEvent = (ItemStateEvent) event;
124 synchronized (this) {
126 double newDutycycle = getDutyCycleValueInPercent(changedEvent.getItemState());
127 double newDutycycleBeforeLimit = newDutycycle;
129 restartDeadManSwitchTimer();
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) {
138 return Math.max(minDutycycle, newDutyCycleFinal1);
140 }).orElse(newDutycycle);
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) {
148 return newDutyCycleFinal2;
150 }).orElse(newDutycycle);
152 logger.debug("Received new duty cycle: {} {}", newDutycycleBeforeLimit,
153 newDutycycle != newDutycycleBeforeLimit ? "Limited to: " + newDutycycle : "");
155 StateMachine localStateMachine = stateMachine;
156 if (localStateMachine != null) {
157 localStateMachine.setDutycycle(newDutycycle);
159 logger.debug("Initialization not finished");
161 } catch (PWMException e) {
162 logger.warn("{}", e.getMessage());
167 private void restartDeadManSwitchTimer() {
168 ScheduledFuture<?> timer = deadMeanSwitchTimer;
173 deadManSwitchTimeoutMs.ifPresent(timeout -> {
174 deadMeanSwitchTimer = getCallback().getScheduler().schedule(this::activateDeadManSwitch,
175 timeout.longValue(), TimeUnit.MILLISECONDS);
179 private void activateDeadManSwitch() {
180 logger.warn("Dead-man switch activated. Disabling output");
182 StateMachine localStateMachine = stateMachine;
183 if (localStateMachine != null) {
184 localStateMachine.stop();
188 private void setOutput(boolean enable) {
189 getCallback().triggered(module, Collections.singletonMap(OUTPUT, OnOffType.from(enable)));
192 private TriggerHandlerCallback getCallback() {
193 ModuleHandlerCallback localCallback = callback;
194 if (localCallback != null && localCallback instanceof TriggerHandlerCallback) {
195 return (TriggerHandlerCallback) localCallback;
198 throw new IllegalStateException();
201 private double getDutyCycleValueInPercent(State state) throws PWMException {
202 if (state instanceof DecimalType) {
203 return ((DecimalType) state).doubleValue();
204 } else if (state instanceof StringType) {
206 return Integer.parseInt(state.toString());
207 } catch (NumberFormatException e) {
210 } else if (state instanceof UnDefType) {
211 throw new PWMException("Duty cycle item '" + dutyCycleItem.getName() + "' has no valid value");
213 throw new PWMException("Duty cycle item not of type DecimalType: " + state.getClass().getSimpleName());
217 public Set<String> getSubscribedEventTypes() {
218 return SUBSCRIBED_EVENT_TYPES;
222 public @Nullable EventFilter getEventFilter() {
227 public void dispose() {
228 ServiceRegistration<?> localEventSubscriberRegistration = eventSubscriberRegistration;
229 if (localEventSubscriberRegistration != null) {
230 localEventSubscriberRegistration.unregister();
233 StateMachine localStateMachine = stateMachine;
234 if (localStateMachine != null) {
235 localStateMachine.stop();