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.handler;
15 import static org.openhab.binding.dmx.internal.DmxBindingConstants.*;
17 import java.util.ArrayList;
18 import java.util.List;
20 import java.util.stream.IntStream;
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.openhab.binding.dmx.internal.DmxBindingConstants.ListenerType;
24 import org.openhab.binding.dmx.internal.DmxBridgeHandler;
25 import org.openhab.binding.dmx.internal.DmxThingHandler;
26 import org.openhab.binding.dmx.internal.Util;
27 import org.openhab.binding.dmx.internal.ValueSet;
28 import org.openhab.binding.dmx.internal.action.FadeAction;
29 import org.openhab.binding.dmx.internal.config.ColorThingHandlerConfiguration;
30 import org.openhab.binding.dmx.internal.multiverse.BaseDmxChannel;
31 import org.openhab.binding.dmx.internal.multiverse.DmxChannel;
32 import org.openhab.core.library.types.DecimalType;
33 import org.openhab.core.library.types.HSBType;
34 import org.openhab.core.library.types.IncreaseDecreaseType;
35 import org.openhab.core.library.types.OnOffType;
36 import org.openhab.core.library.types.PercentType;
37 import org.openhab.core.thing.Bridge;
38 import org.openhab.core.thing.ChannelUID;
39 import org.openhab.core.thing.Thing;
40 import org.openhab.core.thing.ThingStatus;
41 import org.openhab.core.thing.ThingStatusDetail;
42 import org.openhab.core.thing.ThingTypeUID;
43 import org.openhab.core.types.Command;
44 import org.openhab.core.types.RefreshType;
45 import org.slf4j.Logger;
46 import org.slf4j.LoggerFactory;
49 * The {@link DimmerThingHandler} is responsible for handling commands, which are
52 * @author Jan N. Klug - Initial contribution
55 public class ColorThingHandler extends DmxThingHandler {
56 public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Set.of(THING_TYPE_COLOR);
58 private final Logger logger = LoggerFactory.getLogger(ColorThingHandler.class);
60 private final List<DmxChannel> channels = new ArrayList<>();
62 private final List<Integer> currentValues = new ArrayList<>();
63 private HSBType currentColor = new HSBType();
65 private ValueSet turnOnValue = new ValueSet(0, -1, DmxChannel.MAX_VALUE);
66 private ValueSet turnOffValue = new ValueSet(0, -1, DmxChannel.MIN_VALUE);
68 private int fadeTime = 0;
69 private int dimTime = 0;
71 private boolean dynamicTurnOnValue = false;
72 private boolean isDimming = false;
74 public ColorThingHandler(Thing dimmerThing) {
79 public void handleCommand(ChannelUID channelUID, Command command) {
80 logger.trace("received command {} in channel {}", command, channelUID);
81 ValueSet targetValueSet = new ValueSet(fadeTime, -1);
82 switch (channelUID.getId()) {
83 case CHANNEL_BRIGHTNESS_R:
84 if (command instanceof RefreshType) {
85 logger.trace("sending update on refresh to channel {}:brightness_r", this.thing.getUID());
86 currentValues.set(0, channels.get(0).getValue());
88 updateState(channelUID, Util.toPercentValue(currentValues.get(0)));
91 logger.debug("command {} not supported in channel {}:brightness_r", command.getClass(),
95 case CHANNEL_BRIGHTNESS_G:
96 if (command instanceof RefreshType) {
97 logger.trace("sending update on refresh to channel {}:brightness_g", this.thing.getUID());
98 currentValues.set(1, channels.get(1).getValue());
100 updateState(channelUID, Util.toPercentValue(currentValues.get(1)));
103 logger.debug("command {} not supported in channel {}:brightness_g", command.getClass(),
104 this.thing.getUID());
107 case CHANNEL_BRIGHTNESS_B:
108 if (command instanceof RefreshType) {
109 logger.trace("sending update on refresh to channel {}:brightness_b", this.thing.getUID());
110 currentValues.set(2, channels.get(2).getValue());
111 updateCurrentColor();
112 updateState(channelUID, Util.toPercentValue(currentValues.get(2)));
115 logger.debug("command {} not supported in channel {}:brightness_b", command.getClass(),
116 this.thing.getUID());
119 case CHANNEL_COLOR: {
120 if (command instanceof OnOffType onOffCommand) {
121 logger.trace("adding {} fade to channels in thing {}", command, this.thing.getUID());
122 if (onOffCommand == OnOffType.ON) {
123 targetValueSet = turnOnValue;
125 if (dynamicTurnOnValue) {
127 for (DmxChannel channel : channels) {
128 turnOnValue.addValue(channel.getValue());
130 logger.trace("stored channel values fort next turn-on");
132 targetValueSet = turnOffValue;
134 } else if (command instanceof HSBType hsbCommand) {
135 logger.trace("adding color fade to channels in thing {}", this.thing.getUID());
136 targetValueSet.addValue(hsbCommand.getRed());
137 targetValueSet.addValue(hsbCommand.getGreen());
138 targetValueSet.addValue(hsbCommand.getBlue());
139 } else if ((command instanceof PercentType) || (command instanceof DecimalType)) {
140 logger.trace("adding brightness fade to channels in thing {}", this.thing.getUID());
141 PercentType brightness = (command instanceof PercentType percentCommand) ? percentCommand
142 : Util.toPercentValue(((DecimalType) command).intValue());
143 HSBType targetColor = new HSBType(currentColor.getHue(), currentColor.getSaturation(), brightness);
144 targetValueSet.addValue(targetColor.getRed());
145 targetValueSet.addValue(targetColor.getGreen());
146 targetValueSet.addValue(targetColor.getBlue());
147 } else if (command instanceof IncreaseDecreaseType increaseDecreaseCommand) {
148 if (isDimming && increaseDecreaseCommand.equals(IncreaseDecreaseType.INCREASE)) {
149 logger.trace("stopping fade in thing {}", this.thing.getUID());
150 channels.forEach(DmxChannel::clearAction);
154 logger.trace("starting {} fade in thing {}", command, this.thing.getUID());
156 if (increaseDecreaseCommand.equals(IncreaseDecreaseType.INCREASE)) {
157 targetColor = new HSBType(currentColor.getHue(), currentColor.getSaturation(),
158 PercentType.HUNDRED);
160 targetColor = new HSBType(currentColor.getHue(), currentColor.getSaturation(),
163 targetValueSet.addValue(targetColor.getRed());
164 targetValueSet.addValue(targetColor.getGreen());
165 targetValueSet.addValue(targetColor.getBlue());
166 targetValueSet.setFadeTime(dimTime);
169 } else if (command instanceof RefreshType) {
170 logger.trace("sending update on refresh to channel {}:color", this.thing.getUID());
171 currentValues.set(0, channels.get(0).getValue());
172 currentValues.set(1, channels.get(1).getValue());
173 currentValues.set(2, channels.get(2).getValue());
174 updateCurrentColor();
175 updateState(channelUID, currentColor);
178 logger.debug("command {} not supported in channel {}:color", command.getClass(),
179 this.thing.getUID());
185 logger.debug("channel {} not supported in thing {}", channelUID.getId(), this.thing.getUID());
188 final ValueSet valueSet = targetValueSet;
189 IntStream.range(0, channels.size()).forEach(i -> {
190 channels.get(i).setChannelAction(new FadeAction(valueSet.getFadeTime(), channels.get(i).getValue(),
191 valueSet.getValue(i), valueSet.getHoldTime()));
196 public void initialize() {
197 Bridge bridge = getBridge();
198 DmxBridgeHandler bridgeHandler;
199 if (bridge == null) {
200 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "no bridge assigned");
201 dmxHandlerStatus = ThingStatusDetail.CONFIGURATION_ERROR;
204 bridgeHandler = (DmxBridgeHandler) bridge.getHandler();
205 if (bridgeHandler == null) {
206 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "no bridge handler available");
207 dmxHandlerStatus = ThingStatusDetail.CONFIGURATION_ERROR;
212 ColorThingHandlerConfiguration configuration = getConfig().as(ColorThingHandlerConfiguration.class);
213 if (configuration.dmxid.isEmpty()) {
214 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
215 "DMX channel configuration missing");
216 dmxHandlerStatus = ThingStatusDetail.CONFIGURATION_ERROR;
220 List<BaseDmxChannel> configChannels = BaseDmxChannel.fromString(configuration.dmxid,
221 bridgeHandler.getUniverseId());
222 logger.trace("found {} channels in {}", configChannels.size(), this.thing.getUID());
223 for (BaseDmxChannel channel : configChannels) {
224 channels.add(bridgeHandler.getDmxChannel(channel, this.thing));
226 } catch (IllegalArgumentException e) {
227 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
228 dmxHandlerStatus = ThingStatusDetail.CONFIGURATION_ERROR;
232 currentValues.add(DmxChannel.MIN_VALUE);
233 currentValues.add(DmxChannel.MIN_VALUE);
234 currentValues.add(DmxChannel.MIN_VALUE);
236 fadeTime = configuration.fadetime;
237 logger.trace("setting fadeTime to {} ms in {}", fadeTime, this.thing.getUID());
239 dimTime = configuration.dimtime;
240 logger.trace("setting dimTime to {} ms in {}", fadeTime, this.thing.getUID());
242 String turnOnValueString = fadeTime + ":" + configuration.turnonvalue + ":-1";
243 ValueSet turnOnValue = ValueSet.fromString(turnOnValueString);
244 if (turnOnValue.size() % 3 == 0) {
245 this.turnOnValue = turnOnValue;
246 logger.trace("set turnonvalue to {} in {}", turnOnValue, this.thing.getUID());
248 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "turn-on value malformed");
249 dmxHandlerStatus = ThingStatusDetail.CONFIGURATION_ERROR;
252 this.turnOnValue.setFadeTime(fadeTime);
254 dynamicTurnOnValue = configuration.dynamicturnonvalue;
256 String turnOffValueString = fadeTime + ":" + configuration.turnoffvalue + ":-1";
257 ValueSet turnOffValue = ValueSet.fromString(turnOffValueString);
258 if (turnOffValue.size() % 3 == 0) {
259 this.turnOffValue = turnOffValue;
260 logger.trace("set turnoffvalue to {} in {}", turnOffValue, this.thing.getUID());
262 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "turn-off value malformed");
263 dmxHandlerStatus = ThingStatusDetail.CONFIGURATION_ERROR;
267 this.turnOffValue.setFadeTime(fadeTime);
269 // register feedback listeners
270 channels.get(0).addListener(new ChannelUID(this.thing.getUID(), CHANNEL_BRIGHTNESS_R), this,
272 channels.get(1).addListener(new ChannelUID(this.thing.getUID(), CHANNEL_BRIGHTNESS_G), this,
274 channels.get(2).addListener(new ChannelUID(this.thing.getUID(), CHANNEL_BRIGHTNESS_B), this,
277 if (bridge.getStatus().equals(ThingStatus.ONLINE)) {
278 updateStatus(ThingStatus.ONLINE);
279 dmxHandlerStatus = ThingStatusDetail.NONE;
281 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
286 public void dispose() {
287 if (!channels.isEmpty()) {
288 channels.get(0).removeListener(new ChannelUID(this.thing.getUID(), CHANNEL_BRIGHTNESS_R));
289 channels.get(1).removeListener(new ChannelUID(this.thing.getUID(), CHANNEL_BRIGHTNESS_G));
290 channels.get(2).removeListener(new ChannelUID(this.thing.getUID(), CHANNEL_BRIGHTNESS_B));
293 currentValues.clear();
294 currentColor = new HSBType();
298 public void updateChannelValue(ChannelUID channelUID, int value) {
299 updateState(channelUID, Util.toPercentValue(value));
300 switch (channelUID.getId()) {
301 case CHANNEL_BRIGHTNESS_R:
302 currentValues.set(0, value);
304 case CHANNEL_BRIGHTNESS_G:
305 currentValues.set(1, value);
307 case CHANNEL_BRIGHTNESS_B:
308 currentValues.set(2, value);
311 logger.debug("don't know how to handle {} in RGB type", channelUID.getId());
314 updateCurrentColor();
315 updateState(new ChannelUID(this.thing.getUID(), CHANNEL_COLOR), currentColor);
316 logger.trace("received update {} in channel {}, result is {}", value, channelUID, currentColor);
319 private void updateCurrentColor() {
320 currentColor = HSBType.fromRGB(currentValues.get(0), currentValues.get(1), currentValues.get(2));