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.eclipse.jdt.annotation.NonNullByDefault;
21 import org.openhab.core.thing.Thing;
22 import org.slf4j.Logger;
23 import org.slf4j.LoggerFactory;
26 * The {@link Universe} represents a single DMX universes with all its channels and provides a buffer for sending by the
29 * @author Jan N. Klug - Initial contribution
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;
37 private final Logger logger = LoggerFactory.getLogger(Universe.class);
38 private final ReentrantLock universeLock = new ReentrantLock();
40 private int universeId;
41 private int bufferSize = MIN_UNIVERSE_SIZE;
43 private final short[] buffer = new short[MAX_UNIVERSE_SIZE];
44 private final short[] cie1931Curve = new short[DmxChannel.MAX_VALUE << 8 + 1];
46 private long bufferChanged;
47 private int refreshTime = DEFAULT_REFRESH_TIME;
49 private final List<DmxChannel> channels = new ArrayList<>();
50 private final List<Integer> applyCurve = new ArrayList<>();
53 * universe constructor
55 * @param universeId the universe id
57 public Universe(int universeId) {
58 this.universeId = universeId;
59 fillDimCurveLookupTable();
63 * universe constructor with default universe 1
70 * register a channel in the universe, create if not existing
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
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);
84 DmxChannel channel = new DmxChannel(baseChannel, refreshTime);
86 channel.registerThing(thing);
87 logger.debug("creating and returning channel {}", channel);
92 * unregister thing from a channel (deletes channel if not used anymore)
94 * @param thing the thing to unregister
96 public synchronized void unregisterChannels(Thing thing) {
99 Iterator<DmxChannel> channelIterator = channels.iterator();
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);
110 universeLock.unlock();
115 * add an existing channel to this universe
117 * @param channel a {@link DmxChannel} object within this universe
119 private void addChannel(DmxChannel channel) throws IllegalArgumentException {
120 if (universeId == channel.getUniverseId()) {
123 channels.add(channel);
124 if (channel.getChannelId() > bufferSize) {
125 bufferSize = channel.getChannelId();
128 universeLock.unlock();
131 throw new IllegalArgumentException(
132 String.format("Adding channel %s to universe %d not possible", channel.toString(), universeId));
137 * get the timestamp of the last buffer change
141 public long getLastBufferChanged() {
142 return bufferChanged;
146 * get size of the buffer
148 * @return value between {@link #MIN_UNIVERSE_SIZE} and 512
150 public int getBufferSize() {
157 * @return this universe DMX id
159 public int getUniverseId() {
166 * @param universeId new universe id
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);
177 * calculate this universe buffer (run all channel actions) for a given time
179 * @param time the timestamp used for calculation
181 public void calculateBuffer(long time) {
184 for (DmxChannel channel : channels) {
185 logger.trace("calculating new value for {}", channel);
186 int channelId = channel.getChannelId();
187 int vx = channel.getNewHiResValue(time);
189 if (applyCurve.contains(channelId)) {
190 value = cie1931Curve[vx];
194 if (buffer[channelId - 1] != value) {
195 buffer[channelId - 1] = (short) value;
196 bufferChanged = time;
200 universeLock.unlock();
205 * get the full universe buffer
207 * @return byte array with channel values
209 public byte[] getBuffer() {
210 byte[] b = new byte[bufferSize];
213 for (int i = 0; i < bufferSize; i++) {
214 b[i] = (byte) buffer[i];
217 universeLock.unlock();
223 * set list of channels that should use the LED dim curve
227 public void setDimCurveChannels(String listString) {
229 for (BaseDmxChannel channel : BaseDmxChannel.fromString(listString, universeId)) {
230 applyCurve.add(channel.getChannelId());
232 logger.debug("applying dim curve in universe {} to channels {}", universeId, applyCurve);
236 * calculate dim curve table for fast lookup
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
242 int maxValue = DmxChannel.MAX_VALUE << 8;
243 for (int i = 0; i <= maxValue; i++) {
244 float lLn = ((float) i) / maxValue;
246 cie1931Curve[i] = (short) Math.round(DmxChannel.MAX_VALUE * lLn / 9.033);
248 cie1931Curve[i] = (short) Math.round(DmxChannel.MAX_VALUE * Math.pow((lLn + 0.16) / 1.16, 3));
254 * set channel refresh time
256 * @param refreshTime time in ms between state updates for a DMX channel
258 public void setRefreshTime(int refreshTime) {
259 this.refreshTime = refreshTime;