2 * Copyright (c) 2010-2023 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.binding.dmx.internal.multiverse;
15 import java.util.ArrayList;
16 import java.util.Iterator;
17 import java.util.List;
18 import java.util.concurrent.locks.ReentrantLock;
20 import org.openhab.core.thing.Thing;
21 import org.slf4j.Logger;
22 import org.slf4j.LoggerFactory;
25 * The {@link Universe} represents a single DMX universes with all its channels and provides a buffer for sending by the
28 * @author Jan N. Klug - Initial contribution
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;
35 private final Logger logger = LoggerFactory.getLogger(Universe.class);
36 private final ReentrantLock universeLock = new ReentrantLock();
38 private int universeId;
39 private int bufferSize = MIN_UNIVERSE_SIZE;
41 private final short[] buffer = new short[MAX_UNIVERSE_SIZE];
42 private final short[] cie1931Curve = new short[DmxChannel.MAX_VALUE << 8 + 1];
44 private long bufferChanged;
45 private int refreshTime = DEFAULT_REFRESH_TIME;
47 private final List<DmxChannel> channels = new ArrayList<>();
48 private final List<Integer> applyCurve = new ArrayList<>();
51 * universe constructor
53 * @param universeId the universe id
55 public Universe(int universeId) {
56 this.universeId = universeId;
57 fillDimCurveLookupTable();
61 * universe constructor with default universe 1
68 * register a channel in the universe, create if not existing
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
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);
82 DmxChannel channel = new DmxChannel(baseChannel, refreshTime);
84 channel.registerThing(thing);
85 logger.debug("creating and returning channel {}", channel);
90 * unregister thing from a channel (deletes channel if not used anymore)
92 * @param thing the thing to unregister
94 public synchronized void unregisterChannels(Thing thing) {
97 Iterator<DmxChannel> channelIterator = channels.iterator();
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);
108 universeLock.unlock();
113 * add an existing channel to this universe
115 * @param channel a {@link DmxChannel} object within this universe
117 private void addChannel(DmxChannel channel) throws IllegalArgumentException {
118 if (universeId == channel.getUniverseId()) {
121 channels.add(channel);
122 if (channel.getChannelId() > bufferSize) {
123 bufferSize = channel.getChannelId();
126 universeLock.unlock();
129 throw new IllegalArgumentException(
130 String.format("Adding channel %s to universe %d not possible", channel.toString(), universeId));
135 * get the timestamp of the last buffer change
139 public long getLastBufferChanged() {
140 return bufferChanged;
144 * get size of the buffer
146 * @return value between {@link MIN_UNIVERSE_SIZE} and 512
148 public int getBufferSize() {
155 * @return this universe DMX id
157 public int getUniverseId() {
164 * @param universeId new universe id
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);
175 * calculate this universe buffer (run all channel actions) for a given time
177 * @param time the timestamp used for calculation
179 public void calculateBuffer(long time) {
182 for (DmxChannel channel : channels) {
183 logger.trace("calculating new value for {}", channel);
184 int channelId = channel.getChannelId();
185 int vx = channel.getNewHiResValue(time);
187 if (applyCurve.contains(channelId)) {
188 value = cie1931Curve[vx];
192 if (buffer[channelId - 1] != value) {
193 buffer[channelId - 1] = (short) value;
194 bufferChanged = time;
198 universeLock.unlock();
203 * get the full universe buffer
205 * @return byte array with channel values
207 public byte[] getBuffer() {
208 byte[] b = new byte[bufferSize];
211 for (int i = 0; i < bufferSize; i++) {
212 b[i] = (byte) buffer[i];
215 universeLock.unlock();
221 * set list of channels that should use the LED dim curve
225 public void setDimCurveChannels(String listString) {
227 for (BaseDmxChannel channel : BaseDmxChannel.fromString(listString, universeId)) {
228 applyCurve.add(channel.getChannelId());
230 logger.debug("applying dim curve in universe {} to channels {}", universeId, applyCurve);
234 * calculate dim curve table for fast lookup
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
240 int maxValue = DmxChannel.MAX_VALUE << 8;
241 for (int i = 0; i <= maxValue; i++) {
242 float lLn = ((float) i) / maxValue;
244 cie1931Curve[i] = (short) Math.round(DmxChannel.MAX_VALUE * lLn / 9.033);
246 cie1931Curve[i] = (short) Math.round(DmxChannel.MAX_VALUE * Math.pow((lLn + 0.16) / 1.16, 3));
252 * set channel refresh time
254 * @param refreshTime time in ms between state updates for a DMX channel
256 public void setRefreshTime(int refreshTime) {
257 this.refreshTime = refreshTime;