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