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