]> git.basschouten.com Git - openhab-addons.git/blob
50c9a1287c074a71f2c6557af1b9a9885127cf18
[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.Iterator;
17 import java.util.List;
18 import java.util.concurrent.locks.ReentrantLock;
19
20 import org.eclipse.jdt.annotation.NonNullByDefault;
21 import org.openhab.core.thing.Thing;
22 import org.slf4j.Logger;
23 import org.slf4j.LoggerFactory;
24
25 /**
26  * The {@link Universe} represents a single DMX universes with all its channels and provides a buffer for sending by the
27  * bridges
28  *
29  * @author Jan N. Klug - Initial contribution
30  */
31 @NonNullByDefault
32 public class Universe {
33     public static final int MIN_UNIVERSE_SIZE = 32;
34     public static final int MAX_UNIVERSE_SIZE = 512;
35     public static final int DEFAULT_REFRESH_TIME = 1000;
36
37     private final Logger logger = LoggerFactory.getLogger(Universe.class);
38     private final ReentrantLock universeLock = new ReentrantLock();
39
40     private int universeId;
41     private int bufferSize = MIN_UNIVERSE_SIZE;
42
43     private final short[] buffer = new short[MAX_UNIVERSE_SIZE];
44     private final short[] cie1931Curve = new short[DmxChannel.MAX_VALUE << 8 + 1];
45
46     private long bufferChanged;
47     private int refreshTime = DEFAULT_REFRESH_TIME;
48
49     private final List<DmxChannel> channels = new ArrayList<>();
50     private final List<Integer> applyCurve = new ArrayList<>();
51
52     /**
53      * universe constructor
54      *
55      * @param universeId the universe id
56      */
57     public Universe(int universeId) {
58         this.universeId = universeId;
59         fillDimCurveLookupTable();
60     }
61
62     /**
63      * universe constructor with default universe 1
64      */
65     public Universe() {
66         this(1);
67     }
68
69     /**
70      * register a channel in the universe, create if not existing
71      *
72      * @param baseChannel the channel represented by a {@link BaseDmxChannel} object
73      * @param thing the thing to register this channel to
74      * @return a full featured channel
75      */
76     public synchronized DmxChannel registerChannel(BaseDmxChannel baseChannel, Thing thing) {
77         for (DmxChannel channel : channels) {
78             if (baseChannel.compareTo(channel) == 0) {
79                 logger.trace("returning existing channel {}", channel);
80                 channel.registerThing(thing);
81                 return channel;
82             }
83         }
84         DmxChannel channel = new DmxChannel(baseChannel, refreshTime);
85         addChannel(channel);
86         channel.registerThing(thing);
87         logger.debug("creating and returning channel {}", channel);
88         return channel;
89     }
90
91     /**
92      * unregister thing from a channel (deletes channel if not used anymore)
93      *
94      * @param thing the thing to unregister
95      */
96     public synchronized void unregisterChannels(Thing thing) {
97         universeLock.lock();
98         try {
99             Iterator<DmxChannel> channelIterator = channels.iterator();
100
101             while (channelIterator.hasNext()) {
102                 DmxChannel channel = channelIterator.next();
103                 channel.unregisterThing(thing);
104                 if (!channel.hasRegisteredThings()) {
105                     channelIterator.remove();
106                     logger.trace("Removing channel {}, no more things", channel);
107                 }
108             }
109         } finally {
110             universeLock.unlock();
111         }
112     }
113
114     /**
115      * add an existing channel to this universe
116      *
117      * @param channel a {@link DmxChannel} object within this universe
118      */
119     private void addChannel(DmxChannel channel) throws IllegalArgumentException {
120         if (universeId == channel.getUniverseId()) {
121             universeLock.lock();
122             try {
123                 channels.add(channel);
124                 if (channel.getChannelId() > bufferSize) {
125                     bufferSize = channel.getChannelId();
126                 }
127             } finally {
128                 universeLock.unlock();
129             }
130         } else {
131             throw new IllegalArgumentException(
132                     String.format("Adding channel %s to universe %d not possible", channel.toString(), universeId));
133         }
134     }
135
136     /**
137      * get the timestamp of the last buffer change
138      *
139      * @return timestamp
140      */
141     public long getLastBufferChanged() {
142         return bufferChanged;
143     }
144
145     /**
146      * get size of the buffer
147      *
148      * @return value between {@link #MIN_UNIVERSE_SIZE} and 512
149      */
150     public int getBufferSize() {
151         return bufferSize;
152     }
153
154     /**
155      * get universe id
156      *
157      * @return this universe DMX id
158      */
159     public int getUniverseId() {
160         return universeId;
161     }
162
163     /**
164      * change universe id
165      *
166      * @param universeId new universe id
167      */
168     public void rename(int universeId) {
169         logger.debug("Renaming universe {} to {}", this.universeId, universeId);
170         this.universeId = universeId;
171         for (DmxChannel channel : channels) {
172             channel.setUniverseId(universeId);
173         }
174     }
175
176     /**
177      * calculate this universe buffer (run all channel actions) for a given time
178      *
179      * @param time the timestamp used for calculation
180      */
181     public void calculateBuffer(long time) {
182         universeLock.lock();
183         try {
184             for (DmxChannel channel : channels) {
185                 logger.trace("calculating new value for {}", channel);
186                 int channelId = channel.getChannelId();
187                 int vx = channel.getNewHiResValue(time);
188                 int value;
189                 if (applyCurve.contains(channelId)) {
190                     value = cie1931Curve[vx];
191                 } else {
192                     value = vx >> 8;
193                 }
194                 if (buffer[channelId - 1] != value) {
195                     buffer[channelId - 1] = (short) value;
196                     bufferChanged = time;
197                 }
198             }
199         } finally {
200             universeLock.unlock();
201         }
202     }
203
204     /**
205      * get the full universe buffer
206      *
207      * @return byte array with channel values
208      */
209     public byte[] getBuffer() {
210         byte[] b = new byte[bufferSize];
211         universeLock.lock();
212         try {
213             for (int i = 0; i < bufferSize; i++) {
214                 b[i] = (byte) buffer[i];
215             }
216         } finally {
217             universeLock.unlock();
218         }
219         return b;
220     }
221
222     /**
223      * set list of channels that should use the LED dim curve
224      *
225      * @param listString
226      */
227     public void setDimCurveChannels(String listString) {
228         applyCurve.clear();
229         for (BaseDmxChannel channel : BaseDmxChannel.fromString(listString, universeId)) {
230             applyCurve.add(channel.getChannelId());
231         }
232         logger.debug("applying dim curve in universe {} to channels {}", universeId, applyCurve);
233     }
234
235     /**
236      * calculate dim curve table for fast lookup
237      */
238     private void fillDimCurveLookupTable() {
239         // formula taken from: Poynton, C.A.: “Gamma” and its Disguises: The Nonlinear Mappings of
240         // Intensity in Perception, CRTs, Film and Video, SMPTE Journal Dec. 1993, pp. 1099 - 1108
241         // inverted
242         int maxValue = DmxChannel.MAX_VALUE << 8;
243         for (int i = 0; i <= maxValue; i++) {
244             float lLn = ((float) i) / maxValue;
245             if (lLn <= 0.08) {
246                 cie1931Curve[i] = (short) Math.round(DmxChannel.MAX_VALUE * lLn / 9.033);
247             } else {
248                 cie1931Curve[i] = (short) Math.round(DmxChannel.MAX_VALUE * Math.pow((lLn + 0.16) / 1.16, 3));
249             }
250         }
251     }
252
253     /**
254      * set channel refresh time
255      *
256      * @param refreshTime time in ms between state updates for a DMX channel
257      */
258     public void setRefreshTime(int refreshTime) {
259         this.refreshTime = refreshTime;
260     }
261 }