]> git.basschouten.com Git - openhab-addons.git/blob
b24d598c54be17846b22a76ddc5972247559c68b
[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.binding.dmx.internal.multiverse;
14
15 import java.util.ArrayList;
16 import java.util.HashMap;
17 import java.util.List;
18 import java.util.Map;
19 import java.util.Map.Entry;
20
21 import org.eclipse.jdt.annotation.NonNullByDefault;
22 import org.eclipse.jdt.annotation.Nullable;
23 import org.openhab.binding.dmx.internal.DmxBindingConstants.ListenerType;
24 import org.openhab.binding.dmx.internal.DmxThingHandler;
25 import org.openhab.binding.dmx.internal.Util;
26 import org.openhab.binding.dmx.internal.action.ActionState;
27 import org.openhab.binding.dmx.internal.action.BaseAction;
28 import org.openhab.core.library.types.OnOffType;
29 import org.openhab.core.library.types.PercentType;
30 import org.openhab.core.thing.ChannelUID;
31 import org.openhab.core.thing.Thing;
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
34
35 /**
36  * The {@link DmxChannel} extends {@link BaseDmxChannel} with actions and values
37  * handlers.
38  *
39  * @author Jan N. Klug - Initial contribution
40  * @author Davy Vanherbergen - Initial contribution
41  */
42 @NonNullByDefault
43 public class DmxChannel extends BaseDmxChannel {
44     public static final int MIN_VALUE = 0;
45     public static final int MAX_VALUE = 255;
46
47     private final Logger logger = LoggerFactory.getLogger(DmxChannel.class);
48
49     private int value = MIN_VALUE;
50     private int suspendedValue = MIN_VALUE;
51     private int lastStateValue = -1;
52
53     private boolean isSuspended = false;
54     private int refreshTime = 0;
55     private long lastStateTimestamp = 0;
56
57     private final List<BaseAction> actions = new ArrayList<>();
58     private final List<BaseAction> suspendedActions = new ArrayList<>();
59     private final List<Thing> registeredThings = new ArrayList<>();
60
61     private final Map<ChannelUID, DmxThingHandler> onOffListeners = new HashMap<>();
62     private final Map<ChannelUID, DmxThingHandler> valueListeners = new HashMap<>();
63     private @Nullable Entry<ChannelUID, DmxThingHandler> actionListener = null;
64
65     public DmxChannel(int universeId, int dmxChannelId, int refreshTime) {
66         super(universeId, dmxChannelId);
67         this.refreshTime = refreshTime;
68     }
69
70     public DmxChannel(BaseDmxChannel dmxChannel, int refreshTime) {
71         super(dmxChannel);
72         this.refreshTime = refreshTime;
73     }
74
75     /**
76      * register a thing with this channel
77      *
78      * @param thing a Thing object
79      */
80     public void registerThing(Thing thing) {
81         if (!registeredThings.contains(thing)) {
82             logger.debug("registering {} for DMX Channel {}", thing, this);
83             registeredThings.add(thing);
84         }
85     }
86
87     /**
88      * unregister a thing from this object
89      *
90      * @param thing a Thing object
91      */
92     public void unregisterThing(Thing thing) {
93         if (registeredThings.contains(thing)) {
94             logger.debug("removing {} from DMX Channel {}", thing, this);
95             registeredThings.remove(thing);
96         }
97     }
98
99     /**
100      * check if DMX Channel has any registered objects
101      *
102      * @return true or false
103      */
104     public boolean hasRegisteredThings() {
105         return !registeredThings.isEmpty();
106     }
107
108     /**
109      * set a DMX channel value
110      *
111      * @param value Integer value (0-255)
112      */
113     public void setValue(int value) {
114         this.value = Util.toDmxValue(value) << 8;
115         logger.trace("set dmx channel {} to value {}", this, this.value >> 8);
116     }
117
118     /**
119      * set a DMX channel value
120      *
121      * @param value PercentType (0-100)
122      */
123     public void setValue(PercentType value) {
124         this.value = Util.toDmxValue(value) << 8;
125         logger.trace("set dmx channel {} to value {}", this, this.value >> 8);
126     }
127
128     /**
129      * get the value of this DMX channel
130      *
131      * @return value as Integer (0-255)
132      */
133     public int getValue() {
134         return Util.toDmxValue(value >> 8);
135     }
136
137     /**
138      * get the high resolution value of this DMX channel
139      *
140      * @return value as Integer (0-65535)
141      */
142     public int getHiResValue() {
143         return value;
144     }
145
146     /**
147      * suspends current value and actions
148      */
149     public synchronized void suspendAction() {
150         if (isSuspended) {
151             logger.info("second suspend for actions in DMX channel {}, previous will be lost", this);
152         } else {
153             logger.trace("suspending actions and value for channel {}", this);
154         }
155
156         suspendedValue = value;
157         suspendedActions.clear();
158         if (hasRunningActions()) {
159             suspendedActions.addAll(actions);
160         }
161
162         isSuspended = true;
163     }
164
165     /**
166      * resumes previously suspended actions
167      */
168     public synchronized void resumeAction() throws IllegalStateException {
169         if (isSuspended) {
170             clearAction();
171             if (!suspendedActions.isEmpty()) {
172                 actions.addAll(suspendedActions);
173                 suspendedActions.clear();
174                 logger.trace("resuming suspended actions for DMX channel {}", this);
175             } else {
176                 value = suspendedValue;
177                 logger.trace("resuming suspended value for DMX channel {}", this);
178             }
179             isSuspended = false;
180         } else {
181             throw new IllegalStateException("trying to resume actions in non-suspended DMX channel " + this.toString());
182         }
183     }
184
185     /**
186      * check suspended state
187      *
188      * @return true or false
189      */
190     public boolean isSuspended() {
191         return isSuspended;
192     }
193
194     /**
195      * clear all running actions
196      */
197     public synchronized void clearAction() {
198         logger.trace("clearing all actions for DMX channel {}", this);
199         actions.clear();
200         // remove action listener
201         Map.Entry<ChannelUID, DmxThingHandler> actionListener = this.actionListener;
202         if (actionListener != null) {
203             actionListener.getValue().updateSwitchState(actionListener.getKey(), OnOffType.OFF);
204             this.actionListener = null;
205         }
206     }
207
208     /**
209      * Replace the current list of channel actions with the provided one.
210      *
211      * @param channelAction action for this channel.
212      */
213     public synchronized void setChannelAction(BaseAction channelAction) {
214         clearAction();
215         actions.add(channelAction);
216         logger.trace("set action {} for DMX channel {}", channelAction, this);
217     }
218
219     /**
220      * Add a channel action to the current list of channel actions.
221      *
222      * @param channelAction action for this channel.
223      */
224     public synchronized void addChannelAction(BaseAction channelAction) {
225         actions.add(channelAction);
226         logger.trace("added action {} to channel {} (total {} actions)", channelAction, this, actions.size());
227     }
228
229     /**
230      * @return true if there are running actions
231      */
232     public boolean hasRunningActions() {
233         return !actions.isEmpty();
234     }
235
236     /**
237      * Move to the next action in the action chain. This method is used by
238      * automatic chains and to manually move to the next action if actions are
239      * set as indefinite (e.g. endless hold). This allows the user to toggle
240      * through a fixed set of values.
241      */
242     public synchronized void switchToNextAction() {
243         // push action to the back of the action list
244         BaseAction action = actions.get(0);
245         actions.remove(0);
246         action.reset();
247         actions.add(action);
248         logger.trace("switching to next action {} on channel {}", actions.get(0), this);
249     }
250
251     /**
252      * Get the new value for this channel as determined by active actions or the
253      * current value.
254      *
255      * @param calculationTime UNIX timestamp
256      * @return value 0-255
257      */
258     public synchronized Integer getNewValue(long calculationTime) {
259         return (getNewHiResValue(calculationTime) >> 8);
260     }
261
262     /**
263      * Get the new value for this channel as determined by active actions or the
264      * current value.
265      *
266      * @param calculationTime UNIX timestamp
267      * @return value 0-65535
268      */
269     public synchronized Integer getNewHiResValue(long calculationTime) {
270         if (hasRunningActions()) {
271             logger.trace("checking actions, list is {}", actions);
272             BaseAction action = actions.get(0);
273             value = action.getNewValue(this, calculationTime);
274             if (action.getState() == ActionState.COMPLETED && hasRunningActions()) {
275                 switchToNextAction();
276             } else if (action.getState() == ActionState.COMPLETEDFINAL) {
277                 clearAction();
278             }
279         }
280
281         // send updates not more than once in a second, and only on value change
282         if ((lastStateValue != value) && (calculationTime - lastStateTimestamp > refreshTime)) {
283             // notify value listeners if value changed
284             for (Entry<ChannelUID, DmxThingHandler> listener : valueListeners.entrySet()) {
285                 int dmxValue = Util.toDmxValue(value >> 8);
286                 (listener.getValue()).updateChannelValue(listener.getKey(), dmxValue);
287                 logger.trace("sending VALUE={} (raw={}) status update to listener {} ({})", dmxValue, value,
288                         listener.getValue(), listener.getKey());
289             }
290
291             // notify on/off listeners if on/off state changed
292             if ((lastStateValue == 0) || (value == 0)) {
293                 OnOffType state = (value == 0) ? OnOffType.OFF : OnOffType.ON;
294                 for (Entry<ChannelUID, DmxThingHandler> listener : onOffListeners.entrySet()) {
295                     (listener.getValue()).updateSwitchState(listener.getKey(), state);
296                     logger.trace("sending ONOFF={} (raw={}), status update to listener {}", state, value,
297                             listener.getKey());
298                 }
299             }
300
301             lastStateValue = value;
302             lastStateTimestamp = calculationTime;
303         }
304
305         return value;
306     }
307
308     /**
309      * add a channel listener for state updates
310      *
311      * @param thingChannel the channel the listener is linked to
312      * @param listener the listener itself
313      */
314     public void addListener(ChannelUID thingChannel, DmxThingHandler listener, ListenerType type) {
315         switch (type) {
316             case VALUE:
317                 if (valueListeners.containsKey(thingChannel)) {
318                     logger.trace("VALUE listener {} already exists in channel {}", thingChannel, this);
319                 } else {
320                     valueListeners.put(thingChannel, listener);
321                     logger.debug("adding VALUE listener {} to channel {}", thingChannel, this);
322                 }
323                 break;
324             case ACTION:
325                 Map.Entry<ChannelUID, DmxThingHandler> actionListener = this.actionListener;
326                 if (actionListener != null) {
327                     logger.info("replacing ACTION listener {} with {} in channel {}", actionListener.getValue(),
328                             listener, this);
329                 } else {
330                     logger.debug("adding ACTION listener {} in channel {}", listener, this);
331                 }
332                 this.actionListener = Map.entry(thingChannel, listener);
333             default:
334         }
335     }
336
337     /**
338      * remove listener from channel
339      *
340      * @param thingChannel the channel that shall no longer receive updates
341      */
342     public void removeListener(ChannelUID thingChannel) {
343         boolean foundListener = false;
344         if (onOffListeners.containsKey(thingChannel)) {
345             onOffListeners.remove(thingChannel);
346             foundListener = true;
347             logger.debug("removing ONOFF listener {} from DMX channel {}", thingChannel, this);
348         }
349         if (valueListeners.containsKey(thingChannel)) {
350             valueListeners.remove(thingChannel);
351             foundListener = true;
352             logger.debug("removing VALUE listener {} from DMX channel {}", thingChannel, this);
353         }
354         Map.Entry<ChannelUID, DmxThingHandler> actionListener = this.actionListener;
355         if (actionListener != null && actionListener.getKey().equals(thingChannel)) {
356             this.actionListener = null;
357             foundListener = true;
358             logger.debug("removing ACTION listener {} from DMX channel {}", thingChannel, this);
359         }
360         if (!foundListener) {
361             logger.trace("listener {} not found in DMX channel {}", thingChannel, this);
362         }
363     }
364 }