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