]> git.basschouten.com Git - openhab-addons.git/commitdiff
[deconz] add support for effects on color lights (#9238)
authorJ-N-K <J-N-K@users.noreply.github.com>
Wed, 9 Dec 2020 22:44:09 +0000 (23:44 +0100)
committerGitHub <noreply@github.com>
Wed, 9 Dec 2020 22:44:09 +0000 (14:44 -0800)
* add support for effects
* add tags
* remove unnecessary constants
* fix state update

Signed-off-by: Jan N. Klug <jan.n.klug@rub.de>
bundles/org.openhab.binding.deconz/README.md
bundles/org.openhab.binding.deconz/src/main/java/org/openhab/binding/deconz/internal/BindingConstants.java
bundles/org.openhab.binding.deconz/src/main/java/org/openhab/binding/deconz/internal/CommandDescriptionProvider.java [new file with mode: 0644]
bundles/org.openhab.binding.deconz/src/main/java/org/openhab/binding/deconz/internal/DeconzHandlerFactory.java
bundles/org.openhab.binding.deconz/src/main/java/org/openhab/binding/deconz/internal/dto/LightState.java
bundles/org.openhab.binding.deconz/src/main/java/org/openhab/binding/deconz/internal/handler/DeconzBaseThingHandler.java
bundles/org.openhab.binding.deconz/src/main/java/org/openhab/binding/deconz/internal/handler/DeconzBridgeHandler.java
bundles/org.openhab.binding.deconz/src/main/java/org/openhab/binding/deconz/internal/handler/LightThingHandler.java
bundles/org.openhab.binding.deconz/src/main/resources/OH-INF/thing/light-thing-types.xml
bundles/org.openhab.binding.deconz/src/test/java/org/openhab/binding/deconz/LightsTest.java

index 01dbdfed0af46863cded95a0cb823caff2d8d901..6e2aa40bfd34bde283d61401b8ee97967bf6551f 100644 (file)
@@ -160,7 +160,9 @@ Other devices support
 | brightness        | Dimmer                   |     R/W     | Brightness of the light               | `dimmablelight`, `colortemperaturelight`        |                                 
 | switch            | Switch                   |     R/W     | State of a ON/OFF device              | `onofflight`                                    |
 | color             | Color                    |     R/W     | Color of an multi-color light         | `colorlight`, `extendedcolorlight`, `lightgroup`|
-| color_temperature | Number                   |     R/W     | Color temperature in kelvin. The value range is determined by each individual light     | `colortemperaturelight`, `extendedcolorlight`, `lightgroup` |
+| color_temperature | Number                   |     R/W     | Color temperature in Kelvin. The value range is determined by each individual light     | `colortemperaturelight`, `extendedcolorlight`, `lightgroup` |
+| effect            | String                   |     R/W     | Effect selection. Allowed commands are set dynamically                                  | `colorlight`                                    |
+| effectSpeed       | Number                   |     R/W     | Effect Speed                          | `colorlight`                                    |
 | lock              | Switch                   |     R/W     | Lock (ON) or unlock (OFF) the doorlock| `doorlock`                                      |                 
 | position          | Rollershutter            |     R/W     | Position of the blind                 | `windowcovering`                                |
 | heatsetpoint      | Number:Temperature       |     R/W     | Target Temperature in °C              | `thermostat`                                    |
@@ -174,6 +176,7 @@ Other devices support
 **NOTE:** For groups `color` and `color_temperature`  are used for sending commands to the group.
 Their state represents the last command send to the group, not necessarily the actual state of the group.
 
+
 ### Trigger Channels
 
 The dimmer switch additionally supports trigger channels.
index 4dfc4f8ab0ff789f175addc4ae960176fc03eda3..6c7238965358194cb7aa24d4e0004076853206c8 100644 (file)
@@ -15,6 +15,7 @@ package org.openhab.binding.deconz.internal;
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.openhab.core.library.types.PercentType;
 import org.openhab.core.thing.ThingTypeUID;
+import org.openhab.core.thing.type.ChannelTypeUID;
 
 /**
  * The {@link BindingConstants} class defines common constants, which are
@@ -112,6 +113,13 @@ public class BindingConstants {
     public static final String CHANNEL_ALL_ON = "all_on";
     public static final String CHANNEL_ANY_ON = "any_on";
     public static final String CHANNEL_LOCK = "lock";
+    public static final String CHANNEL_EFFECT = "effect";
+    public static final String CHANNEL_EFFECT_SPEED = "effectSpeed";
+
+    // channel uids
+    public static final ChannelTypeUID CHANNEL_EFFECT_TYPE_UID = new ChannelTypeUID(BINDING_ID, CHANNEL_EFFECT);
+    public static final ChannelTypeUID CHANNEL_EFFECT_SPEED_TYPE_UID = new ChannelTypeUID(BINDING_ID,
+            CHANNEL_EFFECT_SPEED);
 
     // Thing configuration
     public static final String CONFIG_HOST = "host";
diff --git a/bundles/org.openhab.binding.deconz/src/main/java/org/openhab/binding/deconz/internal/CommandDescriptionProvider.java b/bundles/org.openhab.binding.deconz/src/main/java/org/openhab/binding/deconz/internal/CommandDescriptionProvider.java
new file mode 100644 (file)
index 0000000..1f808ac
--- /dev/null
@@ -0,0 +1,77 @@
+/**
+ * Copyright (c) 2010-2020 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.deconz.internal;
+
+import java.util.Locale;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.core.thing.Channel;
+import org.openhab.core.thing.ChannelUID;
+import org.openhab.core.thing.ThingUID;
+import org.openhab.core.thing.type.DynamicCommandDescriptionProvider;
+import org.openhab.core.types.CommandDescription;
+import org.osgi.service.component.annotations.Component;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Dynamic channel command description provider.
+ * Overrides the command description for the controls, which receive its configuration in the runtime.
+ *
+ * @author Jan N. Klug - Initial contribution
+ */
+@NonNullByDefault
+@Component(service = { DynamicCommandDescriptionProvider.class, CommandDescriptionProvider.class })
+public class CommandDescriptionProvider implements DynamicCommandDescriptionProvider {
+
+    private final Map<ChannelUID, CommandDescription> descriptions = new ConcurrentHashMap<>();
+    private final Logger logger = LoggerFactory.getLogger(CommandDescriptionProvider.class);
+
+    /**
+     * Set a command description for a channel. This description will be used when preparing the channel command by
+     * the framework for presentation. A previous description, if existed, will be replaced.
+     *
+     * @param channelUID
+     *            channel UID
+     * @param description
+     *            state description for the channel
+     */
+    public void setDescription(ChannelUID channelUID, CommandDescription description) {
+        logger.trace("adding command description for channel {}", channelUID);
+        descriptions.put(channelUID, description);
+    }
+
+    /**
+     * remove all descriptions for a given thing
+     *
+     * @param thingUID the thing's UID
+     */
+    public void removeDescriptionsForThing(ThingUID thingUID) {
+        logger.trace("removing state description for thing {}", thingUID);
+        descriptions.entrySet().removeIf(entry -> entry.getKey().getThingUID().equals(thingUID));
+    }
+
+    @Override
+    public @Nullable CommandDescription getCommandDescription(Channel channel,
+            @Nullable CommandDescription originalStateDescription, @Nullable Locale locale) {
+        if (descriptions.containsKey(channel.getUID())) {
+            logger.trace("returning new stateDescription for {}", channel.getUID());
+            return descriptions.get(channel.getUID());
+        } else {
+            return null;
+        }
+    }
+}
index 273e1b6389b3fd62282d47287641e6f7e44ac064..8e005d3adbfa4c460aaeea67f1332dbf4f70c32c 100644 (file)
@@ -55,14 +55,17 @@ public class DeconzHandlerFactory extends BaseThingHandlerFactory {
     private final WebSocketFactory webSocketFactory;
     private final HttpClientFactory httpClientFactory;
     private final StateDescriptionProvider stateDescriptionProvider;
+    private final CommandDescriptionProvider commandDescriptionProvider;
 
     @Activate
     public DeconzHandlerFactory(final @Reference WebSocketFactory webSocketFactory,
             final @Reference HttpClientFactory httpClientFactory,
-            final @Reference StateDescriptionProvider stateDescriptionProvider) {
+            final @Reference StateDescriptionProvider stateDescriptionProvider,
+            final @Reference CommandDescriptionProvider commandDescriptionProvider) {
         this.webSocketFactory = webSocketFactory;
         this.httpClientFactory = httpClientFactory;
         this.stateDescriptionProvider = stateDescriptionProvider;
+        this.commandDescriptionProvider = commandDescriptionProvider;
 
         GsonBuilder gsonBuilder = new GsonBuilder();
         gsonBuilder.registerTypeAdapter(LightType.class, new LightTypeDeserializer());
@@ -85,7 +88,7 @@ public class DeconzHandlerFactory extends BaseThingHandlerFactory {
             return new DeconzBridgeHandler((Bridge) thing, webSocketFactory,
                     new AsyncHttpClient(httpClientFactory.getCommonHttpClient()), gson);
         } else if (LightThingHandler.SUPPORTED_THING_TYPE_UIDS.contains(thingTypeUID)) {
-            return new LightThingHandler(thing, gson, stateDescriptionProvider);
+            return new LightThingHandler(thing, gson, stateDescriptionProvider, commandDescriptionProvider);
         } else if (SensorThingHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)) {
             return new SensorThingHandler(thing, gson);
         } else if (SensorThermostatThingHandler.SUPPORTED_THING_TYPES.contains(thingTypeUID)) {
index 2ac7ae6209a9550f1a545d0acd5681e61c46292f..653a9b85fb0e2d616590e11511d868c5e3aaae8d 100644 (file)
@@ -35,6 +35,7 @@ public class LightState {
     public @Nullable String alert;
     public @Nullable String colormode;
     public @Nullable String effect;
+    public @Nullable Integer effectSpeed;
 
     // depending on the type of light
     public @Nullable Integer hue;
@@ -66,6 +67,7 @@ public class LightState {
         alert = null;
         colormode = null;
         effect = null;
+        effectSpeed = null;
 
         hue = null;
         sat = null;
@@ -81,8 +83,9 @@ public class LightState {
 
     @Override
     public String toString() {
-        return "LightState{reachable=" + reachable + ", on=" + on + ", bri=" + bri + ", alert='" + alert + '\''
-                + ", colormode='" + colormode + '\'' + ", effect='" + effect + '\'' + ", hue=" + hue + ", sat=" + sat
-                + ", ct=" + ct + ", xy=" + Arrays.toString(xy) + ", transitiontime=" + transitiontime + '}';
+        return "LightState{" + "reachable=" + reachable + ", on=" + on + ", bri=" + bri + ", alert='" + alert + '\''
+                + ", colormode='" + colormode + '\'' + ", effect='" + effect + '\'' + ", effectSpeed=" + effectSpeed
+                + ", hue=" + hue + ", sat=" + sat + ", ct=" + ct + ", xy=" + Arrays.toString(xy) + ", transitiontime="
+                + transitiontime + '}';
     }
 }
index 6e310ea8d372f46dafd541953e1fb14d8c9a0dec..2a3ae3e34c9109be3763c8d9fb005f9af23e163b 100644 (file)
@@ -129,6 +129,12 @@ public abstract class DeconzBaseThingHandler<T extends DeconzBaseMessage> extend
         }
     }
 
+    /**
+     * parse the initial state response message
+     *
+     * @param r AsyncHttpClient.Result with the state response result
+     * @return a message of the correct type
+     */
     protected abstract @Nullable T parseStateResponse(AsyncHttpClient.Result r);
 
     /**
index 7955e11fed33fc72bd37084c400e897dd94933d2..60573e8382786e651da0d43fe7dc3e269d5b1a2d 100644 (file)
@@ -126,7 +126,7 @@ public class DeconzBridgeHandler extends BaseBridgeHandler implements WebSocketC
     private void parseAPIKeyResponse(AsyncHttpClient.Result r) {
         if (r.getResponseCode() == 403) {
             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_PENDING,
-                    "Allow authentification for 3rd party apps. Trying again in " + POLL_FREQUENCY_SEC + " seconds");
+                    "Allow authentication for 3rd party apps. Trying again in " + POLL_FREQUENCY_SEC + " seconds");
             stopTimer();
             scheduledFuture = scheduler.schedule(() -> requestApiKey(), POLL_FREQUENCY_SEC, TimeUnit.SECONDS);
         } else if (r.getResponseCode() == 200) {
index 269a98ccaa42e49b7de79e72193ef15c097c3a98..19ad0f71ba48628f660ba79d9d8bd8fa2875a36d 100644 (file)
@@ -16,12 +16,12 @@ import static org.openhab.binding.deconz.internal.BindingConstants.*;
 import static org.openhab.binding.deconz.internal.Util.*;
 
 import java.math.BigDecimal;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
+import java.util.*;
+import java.util.stream.Collectors;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
 import org.eclipse.jdt.annotation.Nullable;
+import org.openhab.binding.deconz.internal.CommandDescriptionProvider;
 import org.openhab.binding.deconz.internal.StateDescriptionProvider;
 import org.openhab.binding.deconz.internal.Util;
 import org.openhab.binding.deconz.internal.dto.DeconzBaseMessage;
@@ -35,11 +35,9 @@ import org.openhab.core.thing.Thing;
 import org.openhab.core.thing.ThingStatus;
 import org.openhab.core.thing.ThingStatusDetail;
 import org.openhab.core.thing.ThingTypeUID;
-import org.openhab.core.types.Command;
-import org.openhab.core.types.RefreshType;
-import org.openhab.core.types.StateDescription;
-import org.openhab.core.types.StateDescriptionFragmentBuilder;
-import org.openhab.core.types.UnDefType;
+import org.openhab.core.thing.binding.builder.ChannelBuilder;
+import org.openhab.core.thing.binding.builder.ThingBuilder;
+import org.openhab.core.types.*;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -71,6 +69,7 @@ public class LightThingHandler extends DeconzBaseThingHandler<LightMessage> {
     private final Logger logger = LoggerFactory.getLogger(LightThingHandler.class);
 
     private final StateDescriptionProvider stateDescriptionProvider;
+    private final CommandDescriptionProvider commandDescriptionProvider;
 
     private long lastCommandExpireTimestamp = 0;
     private boolean needsPropertyUpdate = false;
@@ -85,9 +84,11 @@ public class LightThingHandler extends DeconzBaseThingHandler<LightMessage> {
     private int ctMax = ZCL_CT_MAX;
     private int ctMin = ZCL_CT_MIN;
 
-    public LightThingHandler(Thing thing, Gson gson, StateDescriptionProvider stateDescriptionProvider) {
+    public LightThingHandler(Thing thing, Gson gson, StateDescriptionProvider stateDescriptionProvider,
+            CommandDescriptionProvider commandDescriptionProvider) {
         super(thing, gson, ResourceType.LIGHTS);
         this.stateDescriptionProvider = stateDescriptionProvider;
+        this.commandDescriptionProvider = commandDescriptionProvider;
     }
 
     @Override
@@ -136,6 +137,24 @@ public class LightThingHandler extends DeconzBaseThingHandler<LightMessage> {
                 } else {
                     return;
                 }
+                break;
+            case CHANNEL_EFFECT:
+                if (command instanceof StringType) {
+                    // effect command only allowed for lights that are turned on
+                    newLightState.on = true;
+                    newLightState.effect = command.toString();
+                } else {
+                    return;
+                }
+                break;
+            case CHANNEL_EFFECT_SPEED:
+                if (command instanceof DecimalType) {
+                    newLightState.on = true;
+                    newLightState.effectSpeed = Util.constrainToRange(((DecimalType) command).intValue(), 0, 10);
+                } else {
+                    return;
+                }
+                break;
             case CHANNEL_SWITCH:
             case CHANNEL_LOCK:
                 if (command instanceof OnOffType) {
@@ -161,7 +180,6 @@ public class LightThingHandler extends DeconzBaseThingHandler<LightMessage> {
                     }
                 } else if (command instanceof HSBType) {
                     HSBType hsbCommand = (HSBType) command;
-
                     if ("xy".equals(lightStateCache.colormode)) {
                         PercentType[] xy = hsbCommand.toXY();
                         if (xy.length < 2) {
@@ -249,7 +267,7 @@ public class LightThingHandler extends DeconzBaseThingHandler<LightMessage> {
         if (r.getResponseCode() == 403) {
             return null;
         } else if (r.getResponseCode() == 200) {
-            LightMessage lightMessage = gson.fromJson(r.getBody(), LightMessage.class);
+            LightMessage lightMessage = Objects.requireNonNull(gson.fromJson(r.getBody(), LightMessage.class));
             if (needsPropertyUpdate) {
                 // if we did not receive an ctmin/ctmax, then we probably don't need it
                 needsPropertyUpdate = false;
@@ -276,10 +294,72 @@ public class LightThingHandler extends DeconzBaseThingHandler<LightMessage> {
         if (stateResponse == null) {
             return;
         }
-
+        if (stateResponse.state.effect != null) {
+            checkAndUpdateEffectChannels(stateResponse);
+        }
         messageReceived(config.id, stateResponse);
     }
 
+    private enum EffectLightModel {
+        LIDL_MELINARA,
+        TINT_MUELLER,
+        UNKNOWN;
+    }
+
+    private void checkAndUpdateEffectChannels(LightMessage lightMessage) {
+        EffectLightModel model = EffectLightModel.UNKNOWN;
+        // try to determine which model we have
+        if (lightMessage.manufacturername.equals("_TZE200_s8gkrkxk")) {
+            // the LIDL Melinara string does not report a proper model name
+            model = EffectLightModel.LIDL_MELINARA;
+        } else if (lightMessage.manufacturername.equals("MLI")) {
+            model = EffectLightModel.TINT_MUELLER;
+        } else {
+            logger.info("Could not determine effect light type for thing {}, please request adding support on GitHub.",
+                    thing.getUID());
+        }
+
+        ChannelUID effectChannelUID = new ChannelUID(thing.getUID(), CHANNEL_EFFECT);
+        ChannelUID effectSpeedChannelUID = new ChannelUID(thing.getUID(), CHANNEL_EFFECT_SPEED);
+
+        if (thing.getChannel(CHANNEL_EFFECT) == null) {
+            ThingBuilder thingBuilder = editThing();
+            thingBuilder.withChannel(
+                    ChannelBuilder.create(effectChannelUID, "String").withType(CHANNEL_EFFECT_TYPE_UID).build());
+            if (model == EffectLightModel.LIDL_MELINARA) {
+                // additional channels
+                thingBuilder.withChannel(ChannelBuilder.create(effectSpeedChannelUID, "Number")
+                        .withType(CHANNEL_EFFECT_SPEED_TYPE_UID).build());
+            }
+            updateThing(thingBuilder.build());
+        }
+
+        switch (model) {
+            case LIDL_MELINARA:
+                List<String> options = List.of("none", "steady", "snow", "rainbow", "snake", "tinkle", "fireworks",
+                        "flag", "waves", "updown", "vintage", "fading", "collide", "strobe", "sparkles", "carnival",
+                        "glow");
+                commandDescriptionProvider.setDescription(effectChannelUID,
+                        CommandDescriptionBuilder.create().withCommandOptions(toCommandOptionList(options)).build());
+                break;
+            case TINT_MUELLER:
+                options = List.of("none", "colorloop", "sunset", "party", "worklight", "campfire", "romance",
+                        "nightlight");
+                commandDescriptionProvider.setDescription(effectChannelUID,
+                        CommandDescriptionBuilder.create().withCommandOptions(toCommandOptionList(options)).build());
+                break;
+            default:
+                options = List.of("none", "colorloop");
+                commandDescriptionProvider.setDescription(effectChannelUID,
+                        CommandDescriptionBuilder.create().withCommandOptions(toCommandOptionList(options)).build());
+
+        }
+    }
+
+    private List<CommandOption> toCommandOptionList(List<String> options) {
+        return options.stream().map(c -> new CommandOption(c, c)).collect(Collectors.toList());
+    }
+
     private void valueUpdated(String channelId, LightState newState) {
         Integer bri = newState.bri;
         Integer hue = newState.hue;
@@ -327,6 +407,19 @@ public class LightThingHandler extends DeconzBaseThingHandler<LightMessage> {
                 if (bri != null) {
                     updateState(channelId, toPercentType(bri));
                 }
+                break;
+            case CHANNEL_EFFECT:
+                String effect = newState.effect;
+                if (effect != null) {
+                    updateState(channelId, new StringType(effect));
+                }
+                break;
+            case CHANNEL_EFFECT_SPEED:
+                Integer effectSpeed = newState.effectSpeed;
+                if (effectSpeed != null) {
+                    updateState(channelId, new DecimalType(effectSpeed));
+                }
+                break;
             default:
         }
     }
index 745c2f108273bef93fbae9b5d66f7ee9b53094f0..7de5c8aca410e530f265128d409da845f36414ff 100644 (file)
                <state pattern="%d K" min="15" max="100000" step="100"/>
        </channel-type>
 
+       <channel-type id="effect">
+               <item-type>String</item-type>
+               <label>Effect Channel</label>
+               <tags>
+                       <tag>Lighting</tag>
+               </tags>
+       </channel-type>
+
+       <channel-type id="effectSpeed">
+               <item-type>Number</item-type>
+               <label>Effect Speed Channel</label>
+               <tags>
+                       <tag>Lighting</tag>
+               </tags>
+               <state min="0" max="10" step="1"/>
+       </channel-type>
+
        <channel-type id="alert">
                <item-type>Switch</item-type>
                <label>Alert</label>
index 85df1150b4c95248dec4715ad40b116a20d78099..d45686d8d9f68a1764a8d6cb1b99621cd3b66224 100644 (file)
@@ -28,6 +28,7 @@ import org.junit.jupiter.api.extension.ExtendWith;
 import org.mockito.Mock;
 import org.mockito.Mockito;
 import org.mockito.junit.jupiter.MockitoExtension;
+import org.openhab.binding.deconz.internal.CommandDescriptionProvider;
 import org.openhab.binding.deconz.internal.StateDescriptionProvider;
 import org.openhab.binding.deconz.internal.dto.LightMessage;
 import org.openhab.binding.deconz.internal.handler.LightThingHandler;
@@ -60,6 +61,7 @@ public class LightsTest {
 
     private @Mock @NonNullByDefault({}) ThingHandlerCallback thingHandlerCallback;
     private @Mock @NonNullByDefault({}) StateDescriptionProvider stateDescriptionProvider;
+    private @Mock @NonNullByDefault({}) CommandDescriptionProvider commandDescriptionProvider;
 
     @BeforeEach
     public void initialize() {
@@ -81,7 +83,8 @@ public class LightsTest {
         Thing light = ThingBuilder.create(THING_TYPE_COLOR_TEMPERATURE_LIGHT, thingUID)
                 .withChannel(ChannelBuilder.create(channelUID_bri, "Dimmer").build())
                 .withChannel(ChannelBuilder.create(channelUID_ct, "Number").build()).build();
-        LightThingHandler lightThingHandler = new LightThingHandler(light, gson, stateDescriptionProvider);
+        LightThingHandler lightThingHandler = new LightThingHandler(light, gson, stateDescriptionProvider,
+                commandDescriptionProvider);
         lightThingHandler.setCallback(thingHandlerCallback);
 
         lightThingHandler.messageReceived("", lightMessage);
@@ -102,7 +105,8 @@ public class LightsTest {
         Thing light = ThingBuilder.create(THING_TYPE_COLOR_TEMPERATURE_LIGHT, thingUID).withProperties(properties)
                 .withChannel(ChannelBuilder.create(channelUID_bri, "Dimmer").build())
                 .withChannel(ChannelBuilder.create(channelUID_ct, "Number").build()).build();
-        LightThingHandler lightThingHandler = new LightThingHandler(light, gson, stateDescriptionProvider) {
+        LightThingHandler lightThingHandler = new LightThingHandler(light, gson, stateDescriptionProvider,
+                commandDescriptionProvider) {
             // avoid warning when initializing
             @Override
             public @Nullable Bridge getBridge() {
@@ -125,7 +129,8 @@ public class LightsTest {
 
         Thing light = ThingBuilder.create(THING_TYPE_DIMMABLE_LIGHT, thingUID)
                 .withChannel(ChannelBuilder.create(channelUID_bri, "Dimmer").build()).build();
-        LightThingHandler lightThingHandler = new LightThingHandler(light, gson, stateDescriptionProvider);
+        LightThingHandler lightThingHandler = new LightThingHandler(light, gson, stateDescriptionProvider,
+                commandDescriptionProvider);
         lightThingHandler.setCallback(thingHandlerCallback);
 
         lightThingHandler.messageReceived("", lightMessage);
@@ -142,7 +147,8 @@ public class LightsTest {
 
         Thing light = ThingBuilder.create(THING_TYPE_DIMMABLE_LIGHT, thingUID)
                 .withChannel(ChannelBuilder.create(channelUID_bri, "Dimmer").build()).build();
-        LightThingHandler lightThingHandler = new LightThingHandler(light, gson, stateDescriptionProvider);
+        LightThingHandler lightThingHandler = new LightThingHandler(light, gson, stateDescriptionProvider,
+                commandDescriptionProvider);
         lightThingHandler.setCallback(thingHandlerCallback);
 
         lightThingHandler.messageReceived("", lightMessage);
@@ -159,7 +165,8 @@ public class LightsTest {
 
         Thing light = ThingBuilder.create(THING_TYPE_DIMMABLE_LIGHT, thingUID)
                 .withChannel(ChannelBuilder.create(channelUID_bri, "Dimmer").build()).build();
-        LightThingHandler lightThingHandler = new LightThingHandler(light, gson, stateDescriptionProvider);
+        LightThingHandler lightThingHandler = new LightThingHandler(light, gson, stateDescriptionProvider,
+                commandDescriptionProvider);
         lightThingHandler.setCallback(thingHandlerCallback);
 
         lightThingHandler.messageReceived("", lightMessage);
@@ -176,7 +183,8 @@ public class LightsTest {
 
         Thing light = ThingBuilder.create(THING_TYPE_WINDOW_COVERING, thingUID)
                 .withChannel(ChannelBuilder.create(channelUID_pos, "Rollershutter").build()).build();
-        LightThingHandler lightThingHandler = new LightThingHandler(light, gson, stateDescriptionProvider);
+        LightThingHandler lightThingHandler = new LightThingHandler(light, gson, stateDescriptionProvider,
+                commandDescriptionProvider);
         lightThingHandler.setCallback(thingHandlerCallback);
 
         lightThingHandler.messageReceived("", lightMessage);