]> git.basschouten.com Git - openhab-addons.git/blob
a3eb8b094dd8d32e5f1c9923e5b12cfe2ead0bbb
[openhab-addons.git] /
1 /**
2  * Copyright (c) 2010-2020 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.shelly.internal.handler;
14
15 import static org.openhab.binding.shelly.internal.ShellyBindingConstants.*;
16 import static org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.*;
17 import static org.openhab.binding.shelly.internal.util.ShellyUtils.*;
18
19 import java.util.Map;
20 import java.util.TreeMap;
21
22 import org.eclipse.jdt.annotation.NonNullByDefault;
23 import org.eclipse.jetty.client.HttpClient;
24 import org.openhab.binding.shelly.internal.api.ShellyApiException;
25 import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellySettingsStatus;
26 import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyShortLightStatus;
27 import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyStatusLight;
28 import org.openhab.binding.shelly.internal.api.ShellyApiJsonDTO.ShellyStatusLightChannel;
29 import org.openhab.binding.shelly.internal.api.ShellyDeviceProfile;
30 import org.openhab.binding.shelly.internal.coap.ShellyCoapServer;
31 import org.openhab.binding.shelly.internal.config.ShellyBindingConfiguration;
32 import org.openhab.binding.shelly.internal.util.ShellyTranslationProvider;
33 import org.openhab.core.library.types.DecimalType;
34 import org.openhab.core.library.types.HSBType;
35 import org.openhab.core.library.types.IncreaseDecreaseType;
36 import org.openhab.core.library.types.OnOffType;
37 import org.openhab.core.library.types.PercentType;
38 import org.openhab.core.library.types.StringType;
39 import org.openhab.core.library.unit.SmartHomeUnits;
40 import org.openhab.core.thing.ChannelUID;
41 import org.openhab.core.thing.Thing;
42 import org.openhab.core.types.Command;
43 import org.slf4j.Logger;
44 import org.slf4j.LoggerFactory;
45
46 /**
47  * The {@link ShellyLightHandler} handles light (Bulb, Duo and RGBW2) specific commands and status. All other commands
48  * will be routet of the ShellyBaseHandler.
49  *
50  * @author Markus Michels - Initial contribution
51  */
52 @NonNullByDefault
53 public class ShellyLightHandler extends ShellyBaseHandler {
54     private final Logger logger = LoggerFactory.getLogger(ShellyLightHandler.class);
55     private final Map<Integer, ShellyColorUtils> channelColors;
56
57     /**
58      * Constructor
59      *
60      * @param thing The thing passed by the HandlerFactory
61      * @param bindingConfig configuration of the binding
62      * @param coapServer coap server instance
63      * @param localIP local IP of the openHAB host
64      * @param httpPort port of the openHAB HTTP API
65      */
66     public ShellyLightHandler(final Thing thing, final ShellyTranslationProvider translationProvider,
67             final ShellyBindingConfiguration bindingConfig, final ShellyCoapServer coapServer, final String localIP,
68             int httpPort, final HttpClient httpClient) {
69         super(thing, translationProvider, bindingConfig, coapServer, localIP, httpPort, httpClient);
70         channelColors = new TreeMap<>();
71     }
72
73     @Override
74     public void initialize() {
75         logger.debug("Thing is using  {}", this.getClass());
76         super.initialize();
77     }
78
79     @Override
80     public boolean handleDeviceCommand(ChannelUID channelUID, Command command) throws IllegalArgumentException {
81         String groupName = getString(channelUID.getGroupId());
82         if (groupName.isEmpty()) {
83             throw new IllegalArgumentException("Empty groupName");
84         }
85
86         int lightId = getLightIdFromGroup(groupName);
87         logger.trace("{}: Execute command {} on channel {}, lightId={}", thingName, command, channelUID.getAsString(),
88                 lightId);
89
90         try {
91             ShellyColorUtils oldCol = getCurrentColors(lightId);
92             oldCol.mode = profile.mode;
93             ShellyColorUtils col = new ShellyColorUtils(oldCol);
94
95             boolean update = true;
96             switch (channelUID.getIdWithoutGroup()) {
97                 default: // non-bulb commands will be handled by the generic handler
98                     return false;
99
100                 case CHANNEL_LIGHT_POWER:
101                     logger.debug("{}: Switch light {}", thingName, command);
102                     api.setLightParm(lightId, SHELLY_LIGHT_TURN,
103                             command == OnOffType.ON ? SHELLY_API_ON : SHELLY_API_OFF);
104                     col.power = (OnOffType) command;
105                     requestUpdates(1, false);
106                     update = false;
107                     break;
108                 case CHANNEL_LIGHT_COLOR_MODE:
109                     logger.debug("{}: Select color mode {}", thingName, command);
110                     col.setMode((OnOffType) command == OnOffType.ON ? SHELLY_MODE_COLOR : SHELLY_MODE_WHITE);
111                     break;
112                 case CHANNEL_COLOR_PICKER:
113                     logger.debug("{}: Update colors from color picker", thingName);
114                     update = handleColorPicker(profile, lightId, col, command);
115                     break;
116                 case CHANNEL_COLOR_FULL:
117                     logger.debug("{}: Set colors to {}", thingName, command);
118                     handleFullColor(col, command);
119                     break;
120                 case CHANNEL_COLOR_RED:
121                     col.setRed(setColor(lightId, SHELLY_COLOR_RED, command, SHELLY_MAX_COLOR));
122                     break;
123                 case CHANNEL_COLOR_GREEN:
124                     col.setGreen(setColor(lightId, SHELLY_COLOR_GREEN, command, SHELLY_MAX_COLOR));
125                     break;
126                 case CHANNEL_COLOR_BLUE:
127                     col.setBlue(setColor(lightId, SHELLY_COLOR_BLUE, command, SHELLY_MAX_COLOR));
128                     break;
129                 case CHANNEL_COLOR_WHITE:
130                     col.setWhite(setColor(lightId, SHELLY_COLOR_WHITE, command, SHELLY_MAX_COLOR));
131                     break;
132                 case CHANNEL_COLOR_GAIN:
133                     col.setGain(setColor(lightId, SHELLY_COLOR_GAIN, command, SHELLY_MIN_GAIN, SHELLY_MAX_GAIN));
134                     break;
135                 case CHANNEL_BRIGHTNESS: // only in white mode
136                     if (profile.inColor && !profile.isBulb) {
137                         logger.debug("{}: Not in white mode, brightness not available", thingName);
138                         break;
139                     }
140
141                     int value = -1;
142                     if (command instanceof OnOffType) { // Switch
143                         logger.debug("{}: Switch light {}", thingName, command);
144                         ShellyShortLightStatus light = api.setRelayTurn(lightId,
145                                 command == OnOffType.ON ? SHELLY_API_ON : SHELLY_API_OFF);
146                         col.power = getOnOff(light.ison);
147                         col.setBrightness(light.brightness);
148                         updateChannel(CHANNEL_COLOR_WHITE, CHANNEL_BRIGHTNESS + "$Switch", col.power);
149                         updateChannel(CHANNEL_COLOR_WHITE, CHANNEL_BRIGHTNESS + "$Value",
150                                 toQuantityType(new Double(col.power == OnOffType.ON ? col.brightness : 0), DIGITS_NONE,
151                                         SmartHomeUnits.PERCENT));
152                         update = false;
153                         break;
154                     }
155
156                     if (command instanceof PercentType) {
157                         Float percent = ((PercentType) command).floatValue();
158                         value = percent.intValue(); // 0..100% = 0..100
159                         logger.debug("{}: Set brightness to {}%/{}", thingName, percent, value);
160                     } else if (command instanceof DecimalType) {
161                         value = ((DecimalType) command).intValue();
162                         logger.debug("{}: Set brightness to {} (Integer)", thingName, value);
163                     }
164                     if (value == 0) {
165                         logger.debug("{}: Brightness=0 -> switch light OFF", thingName);
166                         api.setRelayTurn(lightId, SHELLY_API_OFF);
167                         update = false;
168                     } else {
169                         if (command instanceof IncreaseDecreaseType) {
170                             ShellyShortLightStatus light = api.getLightStatus(lightId);
171                             if (((IncreaseDecreaseType) command).equals(IncreaseDecreaseType.INCREASE)) {
172                                 value = Math.min(light.brightness + DIM_STEPSIZE, 100);
173                             } else {
174                                 value = Math.max(light.brightness - DIM_STEPSIZE, 0);
175                             }
176                             logger.trace("{}: Change brightness from {} to {}", thingName, light.brightness, value);
177                         }
178
179                         validateRange("brightness", value, 0, 100);
180                         logger.debug("{}: Changing brightness from {} to {}", thingName, oldCol.brightness, value);
181                         col.setBrightness(value);
182                     }
183                     updateChannel(CHANNEL_GROUP_LIGHT_CONTROL, CHANNEL_LIGHT_POWER,
184                             value > 0 ? OnOffType.ON : OnOffType.OFF);
185                     break;
186
187                 case CHANNEL_COLOR_TEMP:
188                     Integer temp = -1;
189                     if (command instanceof PercentType) {
190                         logger.debug("{}: Set color temp to {}%", thingName, ((PercentType) command).floatValue());
191                         Float percent = ((PercentType) command).floatValue() / 100;
192                         temp = new DecimalType(col.minTemp + ((col.maxTemp - col.minTemp)) * percent).intValue();
193                         logger.debug("{}: Converted color-temp {}% to {}K (from Percent to Integer)", thingName,
194                                 percent, temp);
195                     } else if (command instanceof DecimalType) {
196                         temp = ((DecimalType) command).intValue();
197                         logger.debug("{}: Set color temp to {}K (Integer)", thingName, temp);
198                     }
199                     validateRange(CHANNEL_COLOR_TEMP, temp, col.minTemp, col.maxTemp);
200                     col.setTemp(temp);
201                     col.brightness = -1;
202                     break;
203
204                 case CHANNEL_COLOR_EFFECT:
205                     Integer effect = ((DecimalType) command).intValue();
206                     logger.debug("{}: Set color effect to {}", thingName, effect);
207                     validateRange("effect", effect, SHELLY_MIN_EFFECT, SHELLY_MAX_EFFECT);
208                     col.setEffect(effect.intValue());
209             }
210
211             if (update) {
212                 // check for switching color mode
213                 if (profile.isBulb && !col.mode.isEmpty() && !col.mode.equals(oldCol.mode)) {
214                     logger.debug("{}: Color mode changed from {} to {}, set new mode", thingName, oldCol.mode,
215                             col.mode);
216                     api.setLightMode(col.mode);
217                 }
218
219                 // send changed colors to the device
220                 sendColors(profile, lightId, oldCol, col, config.brightnessAutoOn);
221             }
222             return true;
223         } catch (ShellyApiException e) {
224             logger.debug("{}: Unable to handle command: {}", thingName, e.toString());
225             return false;
226         } catch (IllegalArgumentException e) {
227             logger.debug("{}: Unable to handle command", thingName, e);
228             return false;
229         }
230     }
231
232     private boolean handleColorPicker(ShellyDeviceProfile profile, Integer lightId, ShellyColorUtils col,
233             Command command) throws ShellyApiException {
234         boolean updated = false;
235         if (command instanceof HSBType) {
236             HSBType hsb = (HSBType) command;
237
238             logger.debug("HSB-Info={}, Hue={}, getRGB={}, toRGB={}/{}/{}", hsb, hsb.getHue(),
239                     String.format("0x%08X", hsb.getRGB()), hsb.toRGB()[0], hsb.toRGB()[1], hsb.toRGB()[2]);
240             if (hsb.toString().contains("360,")) {
241                 logger.trace("{}: need to fix the Hue value (360->0)", thingName);
242                 HSBType fixHue = new HSBType(new DecimalType(0), hsb.getSaturation(), hsb.getBrightness());
243                 hsb = fixHue;
244             }
245
246             col.setRed(getColorFromHSB(hsb.getRed()));
247             col.setBlue(getColorFromHSB(hsb.getBlue()));
248             col.setGreen(getColorFromHSB(hsb.getGreen()));
249             col.setBrightness(getColorFromHSB(hsb.getBrightness(), BRIGHTNESS_FACTOR));
250             // white, gain and temp are not part of the HSB color scheme
251             updated = true;
252         } else if (command instanceof PercentType) {
253             if (!profile.inColor || profile.isBulb) {
254                 col.brightness = SHELLY_MAX_BRIGHTNESS * ((PercentType) command).intValue();
255                 updated = true;
256             }
257         } else if (command instanceof OnOffType) {
258             logger.debug("{}: Switch light {}", thingName, command);
259             api.setLightParm(lightId, SHELLY_LIGHT_TURN,
260                     (OnOffType) command == OnOffType.ON ? SHELLY_API_ON : SHELLY_API_OFF);
261             col.power = (OnOffType) command;
262         } else if (command instanceof IncreaseDecreaseType) {
263             if (!profile.inColor || profile.isBulb) {
264                 logger.debug("{}: {} brightness by {}", thingName, command, SHELLY_DIM_STEPSIZE);
265                 PercentType percent = (PercentType) super.getChannelValue(CHANNEL_GROUP_COLOR_CONTROL,
266                         CHANNEL_BRIGHTNESS);
267                 if (percent != null) {
268                     int currentBrightness = percent.intValue() * SHELLY_MAX_BRIGHTNESS;
269                     int newBrightness = currentBrightness;
270                     if (command == IncreaseDecreaseType.DECREASE) {
271                         newBrightness = Math.max(currentBrightness - SHELLY_DIM_STEPSIZE, 0);
272                     } else {
273                         newBrightness = Math.min(currentBrightness + SHELLY_DIM_STEPSIZE, SHELLY_MAX_BRIGHTNESS);
274                     }
275                     col.brightness = newBrightness;
276                     updated = currentBrightness != newBrightness;
277                 }
278             }
279         }
280         return updated;
281     }
282
283     private boolean handleFullColor(ShellyColorUtils col, Command command) throws IllegalArgumentException {
284         String color = command.toString().toLowerCase();
285         if (color.contains(",")) {
286             col.fromRGBW(color);
287         } else if (color.equals(SHELLY_COLOR_RED)) {
288             col.setRGBW(SHELLY_MAX_COLOR, 0, 0, 0);
289         } else if (color.equals(SHELLY_COLOR_GREEN)) {
290             col.setRGBW(0, SHELLY_MAX_COLOR, 0, 0);
291         } else if (color.equals(SHELLY_COLOR_BLUE)) {
292             col.setRGBW(0, 0, SHELLY_MAX_COLOR, 0);
293         } else if (color.equals(SHELLY_COLOR_YELLOW)) {
294             col.setRGBW(SHELLY_MAX_COLOR, SHELLY_MAX_COLOR, 0, 0);
295         } else if (color.equals(SHELLY_COLOR_WHITE)) {
296             col.setRGBW(0, 0, 0, SHELLY_MAX_COLOR);
297             col.setMode(SHELLY_MODE_WHITE);
298         } else {
299             throw new IllegalArgumentException("Invalid full color selection: " + color);
300         }
301         col.setMode(color.equals(SHELLY_MODE_WHITE) ? SHELLY_MODE_WHITE : SHELLY_MODE_COLOR);
302         return true;
303     }
304
305     private ShellyColorUtils getCurrentColors(int lightId) {
306         ShellyColorUtils col;
307         if (!channelColors.containsKey(lightId)) {
308             col = new ShellyColorUtils(); // create a new entry
309             col.setMinMaxTemp(profile.minTemp, profile.maxTemp);
310             channelColors.put(lightId, col);
311             logger.trace("{}: Colors entry created for lightId {}", thingName, lightId);
312         } else {
313             col = channelColors.get(lightId);
314             logger.trace(
315                     "{}: Colors loaded for lightId {}: power={}, RGBW={}/{}/{}/{}, gain={}, brightness={}, color temp={} (min={}, max={}",
316                     thingName, lightId, col.power, col.red, col.green, col.blue, col.white, col.gain, col.brightness,
317                     col.temp, col.minTemp, col.maxTemp);
318         }
319         return col;
320     }
321
322     @Override
323     public boolean updateDeviceStatus(ShellySettingsStatus genericStatus) throws ShellyApiException {
324         if (!profile.isInitialized()) {
325             logger.debug("{}: Device not yet initialized!", thingName);
326             return false;
327         }
328         if (!profile.isLight) {
329             logger.debug("{}: ERROR: Device is not a light. but class ShellyHandlerLight is called!", thingName);
330         }
331
332         ShellyStatusLight status = api.getLightStatus();
333         logger.trace("{}: Updating light status in {} mode, {} channel(s)", thingName, profile.mode,
334                 status.lights.size());
335
336         // In white mode we have multiple channels
337         int lightId = 0;
338         boolean updated = false;
339         for (ShellyStatusLightChannel light : status.lights) {
340             Integer channelId = lightId + 1;
341             String controlGroup = buildControlGroupName(profile, channelId);
342             // The bulb has a combined channel set for color or white mode
343             // The RGBW2 uses 2 different thing types: color=1 channel, white=4 channel
344             if (profile.isBulb) {
345                 updateChannel(CHANNEL_GROUP_LIGHT_CONTROL, CHANNEL_LIGHT_COLOR_MODE, getOnOff(profile.inColor));
346             }
347
348             ShellyColorUtils col = getCurrentColors(lightId);
349             col.power = getOnOff(light.ison);
350
351             // Channel control/timer
352             // ShellyStatusLightChannel light = status.lights.get(i);
353             updated |= updateChannel(controlGroup, CHANNEL_LIGHT_POWER, col.power);
354             updated |= updateChannel(controlGroup, CHANNEL_TIMER_AUTOON, getDecimal(light.autoOn));
355             updated |= updateChannel(controlGroup, CHANNEL_TIMER_AUTOOFF, getDecimal(light.autoOff));
356             updated |= updateInputs(controlGroup, genericStatus, lightId);
357             if (getBool(light.overpower)) {
358                 postEvent(ALARM_TYPE_OVERPOWER, false);
359             }
360
361             if (profile.inColor) {
362                 logger.trace("{}: update color settings", thingName);
363                 col.setRGBW(getInteger(light.red), getInteger(light.green), getInteger(light.blue),
364                         getInteger(light.white));
365                 col.setGain(getInteger(light.gain));
366                 col.setEffect(getInteger(light.effect));
367
368                 String colorGroup = CHANNEL_GROUP_COLOR_CONTROL;
369                 logger.trace("{}: Update channels for group {}: RGBW={}/{}/{}, in %:{}%/{}%/{}%, white={}%, gain={}%",
370                         thingName, colorGroup, col.red, col.green, col.blue, col.percentRed, col.percentGreen,
371                         col.percentBlue, col.percentWhite, col.percentGain);
372                 updated |= updateChannel(colorGroup, CHANNEL_COLOR_RED, col.percentRed);
373                 updated |= updateChannel(colorGroup, CHANNEL_COLOR_GREEN, col.percentGreen);
374                 updated |= updateChannel(colorGroup, CHANNEL_COLOR_BLUE, col.percentBlue);
375                 updated |= updateChannel(colorGroup, CHANNEL_COLOR_WHITE, col.percentWhite);
376                 updated |= updateChannel(colorGroup, CHANNEL_COLOR_GAIN, col.percentGain);
377                 updated |= updateChannel(colorGroup, CHANNEL_COLOR_EFFECT, new DecimalType(col.effect));
378                 setFullColor(colorGroup, col);
379
380                 logger.trace("{}: update {}.color picker", thingName, colorGroup);
381                 updated |= updateChannel(colorGroup, CHANNEL_COLOR_PICKER, col.toHSB());
382             }
383
384             if (!profile.inColor || profile.isBulb) {
385                 String whiteGroup = buildWhiteGroupName(profile, channelId);
386                 col.setBrightness(getInteger(light.brightness));
387                 updated |= updateChannel(whiteGroup, CHANNEL_BRIGHTNESS + "$Switch", col.power);
388                 updated |= updateChannel(whiteGroup, CHANNEL_BRIGHTNESS + "$Value",
389                         toQuantityType(col.power == OnOffType.ON ? col.percentBrightness.doubleValue() : new Double(0),
390                                 DIGITS_NONE, SmartHomeUnits.PERCENT));
391
392                 if ((profile.isBulb || profile.isDuo) && (light.temp != null)) {
393                     col.setTemp(getInteger(light.temp));
394                     updated |= updateChannel(whiteGroup, CHANNEL_COLOR_TEMP, col.percentTemp);
395                     logger.trace("{}: update {}.color picker", thingName, whiteGroup);
396                     updated |= updateChannel(whiteGroup, CHANNEL_COLOR_PICKER, col.toHSB());
397                 }
398             }
399
400             // continue with next light
401             lightId++;
402         }
403         return updated;
404     }
405
406     private Integer setColor(Integer lightId, String colorName, Command command, Integer minValue, Integer maxValue)
407             throws ShellyApiException, IllegalArgumentException {
408         Integer value = -1;
409         logger.debug("{}: Set {} to {} ({})", thingName, colorName, command, command.getClass());
410         if (command instanceof PercentType) {
411             PercentType percent = (PercentType) command;
412             Double v = new Double(maxValue) * percent.doubleValue() / 100.0;
413             value = v.intValue();
414             logger.debug("{}: Value for {} is in percent: {}%={}", thingName, colorName, percent, value);
415         } else if (command instanceof DecimalType) {
416             value = ((DecimalType) command).intValue();
417             logger.debug("Value for {} is a number: {}", colorName, value);
418         } else if (command instanceof OnOffType) {
419             value = ((OnOffType) command).equals(OnOffType.ON) ? SHELLY_MAX_COLOR : SHELLY_MIN_COLOR;
420             logger.debug("{}: Value for {} of type OnOff was converted to {}", thingName, colorName, value);
421         } else {
422             throw new IllegalArgumentException(
423                     "Invalid value type for " + colorName + ": " + value + " / type " + value.getClass());
424         }
425         validateRange(colorName, value, minValue, maxValue);
426         return value.intValue();
427     }
428
429     private Integer setColor(Integer lightId, String colorName, Command command, Integer maxValue)
430             throws ShellyApiException, IllegalArgumentException {
431         return setColor(lightId, colorName, command, 0, maxValue);
432     }
433
434     private void setFullColor(String colorGroup, ShellyColorUtils col) {
435         if ((col.red == SHELLY_MAX_COLOR) && (col.green == SHELLY_MAX_COLOR) && (col.blue == 0)) {
436             updateChannel(colorGroup, CHANNEL_COLOR_FULL, new StringType(SHELLY_COLOR_YELLOW));
437         } else if ((col.red == SHELLY_MAX_COLOR) && (col.green == 0) && (col.blue == 0)) {
438             updateChannel(colorGroup, CHANNEL_COLOR_FULL, new StringType(SHELLY_COLOR_RED));
439         } else if ((col.red == 0) && (col.green == SHELLY_MAX_COLOR) && (col.blue == 0)) {
440             updateChannel(colorGroup, CHANNEL_COLOR_FULL, new StringType(SHELLY_COLOR_GREEN));
441         } else if ((col.red == 0) && (col.green == 0) && (col.blue == SHELLY_MAX_COLOR)) {
442             updateChannel(colorGroup, CHANNEL_COLOR_FULL, new StringType(SHELLY_COLOR_BLUE));
443         } else if ((col.red == 0) && (col.green == 0) && (col.blue == 0) && (col.white == SHELLY_MAX_COLOR)) {
444             updateChannel(colorGroup, CHANNEL_COLOR_FULL, new StringType(SHELLY_COLOR_WHITE));
445         }
446     }
447
448     private void sendColors(ShellyDeviceProfile profile, Integer lightId, ShellyColorUtils oldCol,
449             ShellyColorUtils newCol, boolean autoOn) throws ShellyApiException {
450         // boolean updated = false;
451         Integer channelId = lightId + 1;
452         Map<String, String> parms = new TreeMap<>();
453
454         logger.trace(
455                 "{}: New color settings for channel {}: RGB {}/{}/{}, white={}, gain={}, brightness={}, color-temp={}",
456                 thingName, channelId, newCol.red, newCol.green, newCol.blue, newCol.white, newCol.gain,
457                 newCol.brightness, newCol.temp);
458         if (autoOn && (newCol.brightness >= 0)) {
459             parms.put(SHELLY_LIGHT_TURN, profile.inColor || newCol.brightness > 0 ? SHELLY_API_ON : SHELLY_API_OFF);
460         }
461         if (profile.inColor) {
462             if (oldCol.red != newCol.red || oldCol.green != newCol.green || oldCol.blue != newCol.blue
463                     || oldCol.white != newCol.white) {
464                 logger.debug("{}: Setting RGBW to {}/{}/{}/{}", thingName, newCol.red, newCol.green, newCol.blue,
465                         newCol.white);
466                 parms.put(SHELLY_COLOR_RED, String.valueOf(newCol.red));
467                 parms.put(SHELLY_COLOR_GREEN, String.valueOf(newCol.green));
468                 parms.put(SHELLY_COLOR_BLUE, String.valueOf(newCol.blue));
469                 parms.put(SHELLY_COLOR_WHITE, String.valueOf(newCol.white));
470             }
471         }
472         if ((!profile.inColor) && (oldCol.temp != newCol.temp)) {
473             logger.debug("{}: Setting color temp to {}", thingName, newCol.temp);
474             parms.put(SHELLY_COLOR_TEMP, String.valueOf(newCol.temp));
475         }
476         if (oldCol.gain != newCol.gain) {
477             logger.debug("{}: Setting gain to {}", thingName, newCol.gain);
478             parms.put(SHELLY_COLOR_GAIN, String.valueOf(newCol.gain));
479         }
480         if ((newCol.brightness >= 0) && (!profile.inColor || profile.isBulb)
481                 && (oldCol.brightness != newCol.brightness)) {
482             logger.debug("{}: Setting brightness to {}", thingName, newCol.brightness);
483             parms.put(SHELLY_COLOR_BRIGHTNESS, String.valueOf(newCol.brightness));
484         }
485         if (!oldCol.effect.equals(newCol.effect)) {
486             logger.debug("{}: Setting effect to {}", thingName, newCol.effect);
487             parms.put(SHELLY_COLOR_EFFECT, newCol.effect.toString());
488         }
489         if (parms.size() > 0) {
490             logger.debug("{}: Send light settings: {}", thingName, parms);
491             api.setLightParms(lightId, parms);
492             updateCurrentColors(lightId, newCol);
493         }
494     }
495
496     private void updateCurrentColors(int lightId, ShellyColorUtils col) {
497         channelColors.replace(lightId, col);
498         logger.debug("{}: Colors updated for lightId {}: RGBW={}/{}/{}/{}, Sat/Gain={}, Bright={}, Temp={} ", thingName,
499                 lightId, col.red, col.green, col.blue, col.white, col.gain, col.brightness, col.temp);
500     }
501
502     private Integer getColorFromHSB(PercentType colorPercent) {
503         return getColorFromHSB(colorPercent, new Double(SATURATION_FACTOR));
504     }
505
506     private Integer getColorFromHSB(PercentType colorPercent, Double factor) {
507         Double value = new Double(Math.round(colorPercent.doubleValue() * factor));
508         logger.trace("{}: convert {}% into {}/{} (factor={})", thingName, colorPercent, value, value.intValue(),
509                 factor);
510         return value.intValue();
511     }
512 }