]> git.basschouten.com Git - openhab-addons.git/blob
57bec6c5bbb9fabe77f590f6153ebc94b55882ac
[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.handler;
14
15 import static org.openhab.binding.dmx.internal.DmxBindingConstants.*;
16
17 import java.util.ArrayList;
18 import java.util.Collections;
19 import java.util.List;
20 import java.util.Set;
21 import java.util.stream.IntStream;
22
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.TunableWhiteThingHandlerConfiguration;
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.IncreaseDecreaseType;
34 import org.openhab.core.library.types.OnOffType;
35 import org.openhab.core.library.types.PercentType;
36 import org.openhab.core.thing.Bridge;
37 import org.openhab.core.thing.ChannelUID;
38 import org.openhab.core.thing.Thing;
39 import org.openhab.core.thing.ThingStatus;
40 import org.openhab.core.thing.ThingStatusDetail;
41 import org.openhab.core.thing.ThingTypeUID;
42 import org.openhab.core.types.Command;
43 import org.openhab.core.types.RefreshType;
44 import org.slf4j.Logger;
45 import org.slf4j.LoggerFactory;
46
47 /**
48  * The {@link TunableWhiteThingHandler} is responsible for handling commands, which are
49  * sent to the dimmer.
50  *
51  * @author Jan N. Klug - Initial contribution
52  */
53
54 public class TunableWhiteThingHandler extends DmxThingHandler {
55     public static final Set<ThingTypeUID> SUPPORTED_THING_TYPES = Collections.singleton(THING_TYPE_TUNABLEWHITE);
56
57     private final Logger logger = LoggerFactory.getLogger(TunableWhiteThingHandler.class);
58
59     private final List<DmxChannel> channels = new ArrayList<>();
60
61     private final List<Integer> currentValues = new ArrayList<>();
62     private PercentType currentBrightness = PercentType.ZERO;
63     private PercentType currentColorTemperature = new PercentType(50);
64
65     private ValueSet turnOnValue = new ValueSet(0, -1, DmxChannel.MAX_VALUE);
66     private ValueSet turnOffValue = new ValueSet(0, -1, DmxChannel.MIN_VALUE);
67
68     private int fadeTime = 0, dimTime = 0;
69
70     private boolean dynamicTurnOnValue = false;
71     private boolean isDimming = false;
72
73     public TunableWhiteThingHandler(Thing dimmerThing) {
74         super(dimmerThing);
75     }
76
77     @Override
78     public void handleCommand(ChannelUID channelUID, Command command) {
79         logger.trace("received command {} in channel {}", command, channelUID);
80         ValueSet targetValueSet = new ValueSet(fadeTime, -1);
81         switch (channelUID.getId()) {
82             case CHANNEL_BRIGHTNESS: {
83                 if (command instanceof PercentType || command instanceof DecimalType) {
84                     PercentType brightness = (command instanceof PercentType) ? (PercentType) command
85                             : Util.toPercentValue(((DecimalType) command).intValue());
86                     logger.trace("adding fade to channels in thing {}", this.thing.getUID());
87                     targetValueSet.addValue(Util.toDmxValue(
88                             Util.toDmxValue(brightness) * (100 - currentColorTemperature.intValue()) / 100));
89                     targetValueSet.addValue(
90                             Util.toDmxValue(Util.toDmxValue(brightness) * currentColorTemperature.intValue() / 100));
91                 } else if (command instanceof OnOffType) {
92                     logger.trace("adding {} fade to channels in thing {}", command, this.thing.getUID());
93                     if (((OnOffType) command) == OnOffType.ON) {
94                         targetValueSet = turnOnValue;
95                     } else {
96                         if (dynamicTurnOnValue) {
97                             turnOnValue.clear();
98                             for (DmxChannel channel : channels) {
99                                 turnOnValue.addValue(channel.getValue());
100                             }
101                             logger.trace("stored channel values fort next turn-on");
102                         }
103                         targetValueSet = turnOffValue;
104                     }
105                 } else if (command instanceof IncreaseDecreaseType) {
106                     if (isDimming && ((IncreaseDecreaseType) command).equals(IncreaseDecreaseType.INCREASE)) {
107                         logger.trace("stopping fade in thing {}", this.thing.getUID());
108                         channels.forEach(DmxChannel::clearAction);
109                         isDimming = false;
110                         return;
111                     } else {
112                         logger.trace("starting {} fade in thing {}", command, this.thing.getUID());
113                         targetValueSet = ((IncreaseDecreaseType) command).equals(IncreaseDecreaseType.INCREASE)
114                                 ? turnOnValue
115                                 : turnOffValue;
116                         targetValueSet.setFadeTime(dimTime);
117                         isDimming = true;
118                     }
119                 } else if (command instanceof RefreshType) {
120                     logger.trace("sending update on refresh to channel {}:brightness", this.thing.getUID());
121                     currentValues.set(0, channels.get(0).getValue());
122                     currentValues.set(1, channels.get(1).getValue());
123                     updateCurrentBrightnessAndTemperature();
124                     updateState(channelUID, currentBrightness);
125                     return;
126                 } else {
127                     logger.debug("command {} not supported in channel {}:brightness", command.getClass(),
128                             this.thing.getUID());
129                     return;
130                 }
131                 break;
132             }
133             case CHANNEL_BRIGHTNESS_CW:
134                 if (command instanceof RefreshType) {
135                     logger.trace("sending update on refresh to channel {}:brightness_cw", this.thing.getUID());
136                     currentValues.set(0, channels.get(0).getValue());
137                     updateState(channelUID, Util.toPercentValue(currentValues.get(0)));
138                     return;
139                 } else {
140                     logger.debug("command {} not supported in channel {}:brightness_cw", command.getClass(),
141                             this.thing.getUID());
142                     return;
143                 }
144             case CHANNEL_BRIGHTNESS_WW:
145                 if (command instanceof RefreshType) {
146                     logger.trace("sending update on refresh to channel {}:brightness_ww", this.thing.getUID());
147                     currentValues.set(1, channels.get(1).getValue());
148                     updateState(channelUID, Util.toPercentValue(currentValues.get(1)));
149                     return;
150                 } else {
151                     logger.debug("command {} not supported in channel {}:brightness_ww", command.getClass(),
152                             this.thing.getUID());
153                     return;
154                 }
155             case CHANNEL_COLOR_TEMPERATURE: {
156                 if (command instanceof PercentType) {
157                     PercentType colorTemperature = (PercentType) command;
158                     targetValueSet.addValue(Util.toDmxValue(
159                             Util.toDmxValue(currentBrightness) * (100 - colorTemperature.intValue()) / 100));
160                     targetValueSet.addValue(
161                             Util.toDmxValue(Util.toDmxValue(currentBrightness) * colorTemperature.intValue() / 100));
162                 } else if (command instanceof RefreshType) {
163                     logger.trace("sending update on refresh to channel {}:color_temperature", this.thing.getUID());
164                     currentValues.set(0, channels.get(0).getValue());
165                     currentValues.set(1, channels.get(1).getValue());
166                     updateCurrentBrightnessAndTemperature();
167                     updateState(channelUID, currentColorTemperature);
168                     return;
169                 } else {
170                     logger.debug("command {} not supported in channel {}:color_temperature", command.getClass(),
171                             this.thing.getUID());
172                     return;
173                 }
174                 break;
175             }
176             default:
177                 logger.debug("channel {} not supported in thing {}", channelUID.getId(), this.thing.getUID());
178                 return;
179         }
180         final ValueSet valueSet = targetValueSet;
181         IntStream.range(0, channels.size()).forEach(i -> {
182             channels.get(i).setChannelAction(new FadeAction(valueSet.getFadeTime(), channels.get(i).getValue(),
183                     valueSet.getValue(i), valueSet.getHoldTime()));
184         });
185     }
186
187     @Override
188     public void initialize() {
189         Bridge bridge = getBridge();
190         DmxBridgeHandler bridgeHandler;
191         if (bridge == null) {
192             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "no bridge assigned");
193             dmxHandlerStatus = ThingStatusDetail.CONFIGURATION_ERROR;
194             return;
195         } else {
196             bridgeHandler = (DmxBridgeHandler) bridge.getHandler();
197             if (bridgeHandler == null) {
198                 updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "no bridge handler available");
199                 dmxHandlerStatus = ThingStatusDetail.CONFIGURATION_ERROR;
200                 return;
201             }
202         }
203
204         TunableWhiteThingHandlerConfiguration configuration = getConfig()
205                 .as(TunableWhiteThingHandlerConfiguration.class);
206         if (configuration.dmxid.isEmpty()) {
207             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
208                     "DMX channel configuration missing");
209             dmxHandlerStatus = ThingStatusDetail.CONFIGURATION_ERROR;
210             return;
211         }
212         try {
213             List<BaseDmxChannel> configChannels = BaseDmxChannel.fromString(configuration.dmxid,
214                     bridgeHandler.getUniverseId());
215             logger.trace("found {} channels in {}", configChannels.size(), this.thing.getUID());
216             for (BaseDmxChannel channel : configChannels) {
217                 channels.add(bridgeHandler.getDmxChannel(channel, this.thing));
218             }
219         } catch (IllegalArgumentException e) {
220             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, e.getMessage());
221             dmxHandlerStatus = ThingStatusDetail.CONFIGURATION_ERROR;
222             return;
223         }
224
225         currentValues.add(DmxChannel.MIN_VALUE);
226         currentValues.add(DmxChannel.MIN_VALUE);
227
228         fadeTime = configuration.fadetime;
229         logger.trace("setting fadeTime to {} ms in {}", fadeTime, this.thing.getUID());
230
231         dimTime = configuration.dimtime;
232         logger.trace("setting dimTime to {} ms in {}", fadeTime, this.thing.getUID());
233
234         String turnOnValueString = String.valueOf(fadeTime) + ":" + configuration.turnonvalue + ":-1";
235         ValueSet turnOnValue = ValueSet.fromString(turnOnValueString);
236         if (turnOnValue.size() % 2 == 0) {
237             this.turnOnValue = turnOnValue;
238             logger.trace("set turnonvalue to {} in {}", turnOnValue, this.thing.getUID());
239         } else {
240             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "turn-on value malformed");
241             dmxHandlerStatus = ThingStatusDetail.CONFIGURATION_ERROR;
242             return;
243         }
244         this.turnOnValue.setFadeTime(fadeTime);
245
246         dynamicTurnOnValue = configuration.dynamicturnonvalue;
247
248         String turnOffValueString = String.valueOf(fadeTime) + ":" + configuration.turnoffvalue + ":-1";
249         ValueSet turnOffValue = ValueSet.fromString(turnOffValueString);
250         if (turnOffValue.size() % 2 == 0) {
251             this.turnOffValue = turnOffValue;
252             logger.trace("set turnoffvalue to {} in {}", turnOffValue, this.thing.getUID());
253         } else {
254             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "turn-off value malformed");
255             dmxHandlerStatus = ThingStatusDetail.CONFIGURATION_ERROR;
256             return;
257         }
258
259         this.turnOffValue.setFadeTime(fadeTime);
260
261         // register feedback listeners
262         channels.get(0).addListener(new ChannelUID(this.thing.getUID(), CHANNEL_BRIGHTNESS_CW), this,
263                 ListenerType.VALUE);
264         channels.get(1).addListener(new ChannelUID(this.thing.getUID(), CHANNEL_BRIGHTNESS_WW), this,
265                 ListenerType.VALUE);
266
267         if (bridge.getStatus().equals(ThingStatus.ONLINE)) {
268             updateStatus(ThingStatus.ONLINE);
269             dmxHandlerStatus = ThingStatusDetail.NONE;
270         } else {
271             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.BRIDGE_OFFLINE);
272         }
273     }
274
275     @Override
276     public void dispose() {
277         if (!channels.isEmpty()) {
278             channels.get(0).removeListener(new ChannelUID(this.thing.getUID(), CHANNEL_BRIGHTNESS_CW));
279             channels.get(1).removeListener(new ChannelUID(this.thing.getUID(), CHANNEL_BRIGHTNESS_WW));
280         }
281         Bridge bridge = getBridge();
282         if (bridge != null) {
283             DmxBridgeHandler bridgeHandler = (DmxBridgeHandler) bridge.getHandler();
284             if (bridgeHandler != null) {
285                 bridgeHandler.unregisterDmxChannels(this.thing);
286                 logger.debug("removing {} channels from {}", channels.size(), this.thing.getUID());
287             }
288         }
289         channels.clear();
290         currentValues.clear();
291     }
292
293     @Override
294     public void updateChannelValue(ChannelUID channelUID, int value) {
295         updateState(channelUID, Util.toPercentValue(value));
296         switch (channelUID.getId()) {
297             case CHANNEL_BRIGHTNESS_CW:
298                 currentValues.set(0, value);
299                 break;
300             case CHANNEL_BRIGHTNESS_WW:
301                 currentValues.set(1, value);
302                 break;
303             default:
304                 logger.debug("don't know how to handle {} in tunable white type", channelUID.getId());
305                 return;
306         }
307
308         updateCurrentBrightnessAndTemperature();
309         updateState(new ChannelUID(this.thing.getUID(), CHANNEL_BRIGHTNESS), currentBrightness);
310         updateState(new ChannelUID(this.thing.getUID(), CHANNEL_COLOR_TEMPERATURE), currentColorTemperature);
311         logger.trace("received update {} for channel {}, resulting in b={}, ct={}", value, channelUID,
312                 currentBrightness, currentColorTemperature);
313     }
314
315     private void updateCurrentBrightnessAndTemperature() {
316         currentBrightness = Util.toPercentValue(Util.toDmxValue(currentValues.get(0) + currentValues.get(1)));
317         if (!PercentType.ZERO.equals(currentBrightness)) {
318             currentColorTemperature = new PercentType(
319                     100 * currentValues.get(1) / (currentValues.get(0) + currentValues.get(1)));
320         }
321     }
322 }