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 deleted file mode 100644 index 143d827ce2f..00000000000 --- a/bundles/org.openhab.binding.deconz/src/main/java/org/openhab/binding/deconz/internal/CommandDescriptionProvider.java +++ /dev/null @@ -1,77 +0,0 @@ -/** - * Copyright (c) 2010-2021 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 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; - } - } -} diff --git a/bundles/org.openhab.binding.deconz/src/main/java/org/openhab/binding/deconz/internal/DeconzDynamicCommandDescriptionProvider.java b/bundles/org.openhab.binding.deconz/src/main/java/org/openhab/binding/deconz/internal/DeconzDynamicCommandDescriptionProvider.java new file mode 100644 index 00000000000..5f33194773a --- /dev/null +++ b/bundles/org.openhab.binding.deconz/src/main/java/org/openhab/binding/deconz/internal/DeconzDynamicCommandDescriptionProvider.java @@ -0,0 +1,43 @@ +/** + * Copyright (c) 2010-2021 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 org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.thing.ThingUID; +import org.openhab.core.thing.binding.BaseDynamicCommandDescriptionProvider; +import org.openhab.core.thing.type.DynamicCommandDescriptionProvider; +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, DeconzDynamicCommandDescriptionProvider.class }) +public class DeconzDynamicCommandDescriptionProvider extends BaseDynamicCommandDescriptionProvider { + private final Logger logger = LoggerFactory.getLogger(DeconzDynamicCommandDescriptionProvider.class); + + /** + * 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); + channelOptionsMap.entrySet().removeIf(entry -> entry.getKey().getThingUID().equals(thingUID)); + } +} diff --git a/bundles/org.openhab.binding.deconz/src/main/java/org/openhab/binding/deconz/internal/StateDescriptionProvider.java b/bundles/org.openhab.binding.deconz/src/main/java/org/openhab/binding/deconz/internal/DeconzDynamicStateDescriptionProvider.java similarity index 53% rename from bundles/org.openhab.binding.deconz/src/main/java/org/openhab/binding/deconz/internal/StateDescriptionProvider.java rename to bundles/org.openhab.binding.deconz/src/main/java/org/openhab/binding/deconz/internal/DeconzDynamicStateDescriptionProvider.java index 90cda64fff3..e5764991c04 100644 --- a/bundles/org.openhab.binding.deconz/src/main/java/org/openhab/binding/deconz/internal/StateDescriptionProvider.java +++ b/bundles/org.openhab.binding.deconz/src/main/java/org/openhab/binding/deconz/internal/DeconzDynamicStateDescriptionProvider.java @@ -14,6 +14,7 @@ package org.openhab.binding.deconz.internal; import java.util.Locale; import java.util.Map; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import org.eclipse.jdt.annotation.NonNullByDefault; @@ -21,8 +22,11 @@ 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.binding.BaseDynamicStateDescriptionProvider; +import org.openhab.core.thing.events.ThingEventFactory; import org.openhab.core.thing.type.DynamicStateDescriptionProvider; import org.openhab.core.types.StateDescription; +import org.openhab.core.types.StateDescriptionFragment; import org.osgi.service.component.annotations.Component; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -34,11 +38,11 @@ import org.slf4j.LoggerFactory; * @author Jan N. Klug - Initial contribution */ @NonNullByDefault -@Component(service = { DynamicStateDescriptionProvider.class, StateDescriptionProvider.class }) -public class StateDescriptionProvider implements DynamicStateDescriptionProvider { +@Component(service = { DynamicStateDescriptionProvider.class, DeconzDynamicStateDescriptionProvider.class }) +public class DeconzDynamicStateDescriptionProvider extends BaseDynamicStateDescriptionProvider { + private final Logger logger = LoggerFactory.getLogger(DeconzDynamicStateDescriptionProvider.class); - private final Map descriptions = new ConcurrentHashMap<>(); - private final Logger logger = LoggerFactory.getLogger(StateDescriptionProvider.class); + private final Map stateDescriptionFragments = new ConcurrentHashMap<>(); /** * Set a state description for a channel. This description will be used when preparing the channel state by @@ -46,12 +50,18 @@ public class StateDescriptionProvider implements DynamicStateDescriptionProvider * * @param channelUID * channel UID - * @param description + * @param stateDescriptionFragment * state description for the channel */ - public void setDescription(ChannelUID channelUID, StateDescription description) { - logger.trace("adding state description for channel {}", channelUID); - descriptions.put(channelUID, description); + public void setDescriptionFragment(ChannelUID channelUID, StateDescriptionFragment stateDescriptionFragment) { + StateDescriptionFragment oldStateDescriptionFragment = stateDescriptionFragments.get(channelUID); + if (!stateDescriptionFragment.equals(oldStateDescriptionFragment)) { + logger.trace("adding state description for channel {}", channelUID); + stateDescriptionFragments.put(channelUID, stateDescriptionFragment); + postEvent(ThingEventFactory.createChannelDescriptionChangedEvent(channelUID, + itemChannelLinkRegistry != null ? itemChannelLinkRegistry.getLinkedItemNames(channelUID) : Set.of(), + stateDescriptionFragment, oldStateDescriptionFragment)); + } } /** @@ -61,17 +71,18 @@ public class StateDescriptionProvider implements DynamicStateDescriptionProvider */ public void removeDescriptionsForThing(ThingUID thingUID) { logger.trace("removing state description for thing {}", thingUID); - descriptions.entrySet().removeIf(entry -> entry.getKey().getThingUID().equals(thingUID)); + stateDescriptionFragments.entrySet().removeIf(entry -> entry.getKey().getThingUID().equals(thingUID)); } @Override public @Nullable StateDescription getStateDescription(Channel channel, @Nullable StateDescription originalStateDescription, @Nullable Locale locale) { - if (descriptions.containsKey(channel.getUID())) { + StateDescriptionFragment stateDescriptionFragment = stateDescriptionFragments.get(channel.getUID()); + if (stateDescriptionFragment != null) { logger.trace("returning new stateDescription for {}", channel.getUID()); - return descriptions.get(channel.getUID()); + return stateDescriptionFragment.toStateDescription(); } else { - return null; + return super.getStateDescription(channel, originalStateDescription, locale); } } } diff --git a/bundles/org.openhab.binding.deconz/src/main/java/org/openhab/binding/deconz/internal/DeconzHandlerFactory.java b/bundles/org.openhab.binding.deconz/src/main/java/org/openhab/binding/deconz/internal/DeconzHandlerFactory.java index 93f5b974f28..74fe182307c 100644 --- a/bundles/org.openhab.binding.deconz/src/main/java/org/openhab/binding/deconz/internal/DeconzHandlerFactory.java +++ b/bundles/org.openhab.binding.deconz/src/main/java/org/openhab/binding/deconz/internal/DeconzHandlerFactory.java @@ -65,14 +65,14 @@ public class DeconzHandlerFactory extends BaseThingHandlerFactory { private final Gson gson; private final WebSocketFactory webSocketFactory; private final HttpClientFactory httpClientFactory; - private final StateDescriptionProvider stateDescriptionProvider; - private final CommandDescriptionProvider commandDescriptionProvider; + private final DeconzDynamicStateDescriptionProvider stateDescriptionProvider; + private final DeconzDynamicCommandDescriptionProvider commandDescriptionProvider; @Activate public DeconzHandlerFactory(final @Reference WebSocketFactory webSocketFactory, final @Reference HttpClientFactory httpClientFactory, - final @Reference StateDescriptionProvider stateDescriptionProvider, - final @Reference CommandDescriptionProvider commandDescriptionProvider) { + final @Reference DeconzDynamicStateDescriptionProvider stateDescriptionProvider, + final @Reference DeconzDynamicCommandDescriptionProvider commandDescriptionProvider) { this.webSocketFactory = webSocketFactory; this.httpClientFactory = httpClientFactory; this.stateDescriptionProvider = stateDescriptionProvider; diff --git a/bundles/org.openhab.binding.deconz/src/main/java/org/openhab/binding/deconz/internal/dto/Scene.java b/bundles/org.openhab.binding.deconz/src/main/java/org/openhab/binding/deconz/internal/dto/Scene.java index e4e48d5c386..7747d996bb4 100644 --- a/bundles/org.openhab.binding.deconz/src/main/java/org/openhab/binding/deconz/internal/dto/Scene.java +++ b/bundles/org.openhab.binding.deconz/src/main/java/org/openhab/binding/deconz/internal/dto/Scene.java @@ -13,6 +13,7 @@ package org.openhab.binding.deconz.internal.dto; import org.eclipse.jdt.annotation.NonNullByDefault; +import org.openhab.core.types.CommandOption; /** * The {@link Scene} is send by the websocket connection as well as the Rest API. @@ -24,4 +25,13 @@ import org.eclipse.jdt.annotation.NonNullByDefault; public class Scene { public String id = ""; public String name = ""; + + public CommandOption toCommandOption() { + return new CommandOption(name, name); + } + + @Override + public String toString() { + return "Scene{" + "id='" + id + '\'' + ", name='" + name + '\'' + '}'; + } } diff --git a/bundles/org.openhab.binding.deconz/src/main/java/org/openhab/binding/deconz/internal/handler/GroupThingHandler.java b/bundles/org.openhab.binding.deconz/src/main/java/org/openhab/binding/deconz/internal/handler/GroupThingHandler.java index ad2f51409b5..7a30eb98d04 100644 --- a/bundles/org.openhab.binding.deconz/src/main/java/org/openhab/binding/deconz/internal/handler/GroupThingHandler.java +++ b/bundles/org.openhab.binding.deconz/src/main/java/org/openhab/binding/deconz/internal/handler/GroupThingHandler.java @@ -19,12 +19,13 @@ import java.util.Set; import java.util.stream.Collectors; import org.eclipse.jdt.annotation.NonNullByDefault; -import org.openhab.binding.deconz.internal.CommandDescriptionProvider; +import org.openhab.binding.deconz.internal.DeconzDynamicCommandDescriptionProvider; import org.openhab.binding.deconz.internal.Util; import org.openhab.binding.deconz.internal.dto.DeconzBaseMessage; import org.openhab.binding.deconz.internal.dto.GroupAction; import org.openhab.binding.deconz.internal.dto.GroupMessage; import org.openhab.binding.deconz.internal.dto.GroupState; +import org.openhab.binding.deconz.internal.dto.Scene; import org.openhab.binding.deconz.internal.types.ResourceType; import org.openhab.core.library.types.DecimalType; import org.openhab.core.library.types.HSBType; @@ -36,8 +37,6 @@ import org.openhab.core.thing.Thing; import org.openhab.core.thing.ThingStatus; import org.openhab.core.thing.ThingTypeUID; import org.openhab.core.types.Command; -import org.openhab.core.types.CommandDescriptionBuilder; -import org.openhab.core.types.CommandOption; import org.openhab.core.types.RefreshType; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -56,12 +55,13 @@ import com.google.gson.Gson; public class GroupThingHandler extends DeconzBaseThingHandler { public static final Set SUPPORTED_THING_TYPE_UIDS = Set.of(THING_TYPE_LIGHTGROUP); private final Logger logger = LoggerFactory.getLogger(GroupThingHandler.class); - private final CommandDescriptionProvider commandDescriptionProvider; + private final DeconzDynamicCommandDescriptionProvider commandDescriptionProvider; private Map scenes = Map.of(); private GroupState groupStateCache = new GroupState(); - public GroupThingHandler(Thing thing, Gson gson, CommandDescriptionProvider commandDescriptionProvider) { + public GroupThingHandler(Thing thing, Gson gson, + DeconzDynamicCommandDescriptionProvider commandDescriptionProvider) { super(thing, gson, ResourceType.GROUPS); this.commandDescriptionProvider = commandDescriptionProvider; } @@ -142,10 +142,8 @@ public class GroupThingHandler extends DeconzBaseThingHandler { GroupMessage groupMessage = (GroupMessage) stateResponse; scenes = groupMessage.scenes.stream().collect(Collectors.toMap(scene -> scene.name, scene -> scene.id)); ChannelUID channelUID = new ChannelUID(thing.getUID(), CHANNEL_SCENE); - commandDescriptionProvider.setDescription(channelUID, - CommandDescriptionBuilder.create().withCommandOptions(groupMessage.scenes.stream() - .map(scene -> new CommandOption(scene.name, scene.name)).collect(Collectors.toList())) - .build()); + commandDescriptionProvider.setCommandOptions(channelUID, + groupMessage.scenes.stream().map(Scene::toCommandOption).collect(Collectors.toList())); } messageReceived(config.id, stateResponse); diff --git a/bundles/org.openhab.binding.deconz/src/main/java/org/openhab/binding/deconz/internal/handler/LightThingHandler.java b/bundles/org.openhab.binding.deconz/src/main/java/org/openhab/binding/deconz/internal/handler/LightThingHandler.java index 25a267bcba7..dab69ea8100 100644 --- a/bundles/org.openhab.binding.deconz/src/main/java/org/openhab/binding/deconz/internal/handler/LightThingHandler.java +++ b/bundles/org.openhab.binding.deconz/src/main/java/org/openhab/binding/deconz/internal/handler/LightThingHandler.java @@ -24,8 +24,8 @@ 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.DeconzDynamicCommandDescriptionProvider; +import org.openhab.binding.deconz.internal.DeconzDynamicStateDescriptionProvider; import org.openhab.binding.deconz.internal.Util; import org.openhab.binding.deconz.internal.dto.DeconzBaseMessage; import org.openhab.binding.deconz.internal.dto.LightMessage; @@ -49,10 +49,9 @@ import org.openhab.core.thing.ThingTypeUID; import org.openhab.core.thing.binding.builder.ChannelBuilder; import org.openhab.core.thing.binding.builder.ThingBuilder; import org.openhab.core.types.Command; -import org.openhab.core.types.CommandDescriptionBuilder; import org.openhab.core.types.CommandOption; import org.openhab.core.types.RefreshType; -import org.openhab.core.types.StateDescription; +import org.openhab.core.types.StateDescriptionFragment; import org.openhab.core.types.StateDescriptionFragmentBuilder; import org.openhab.core.types.UnDefType; import org.slf4j.Logger; @@ -85,8 +84,8 @@ public class LightThingHandler extends DeconzBaseThingHandler { private final Logger logger = LoggerFactory.getLogger(LightThingHandler.class); - private final StateDescriptionProvider stateDescriptionProvider; - private final CommandDescriptionProvider commandDescriptionProvider; + private final DeconzDynamicStateDescriptionProvider stateDescriptionProvider; + private final DeconzDynamicCommandDescriptionProvider commandDescriptionProvider; private long lastCommandExpireTimestamp = 0; private boolean needsPropertyUpdate = false; @@ -104,8 +103,8 @@ public class LightThingHandler extends DeconzBaseThingHandler { private int ctMax = ZCL_CT_MAX; private int ctMin = ZCL_CT_MIN; - public LightThingHandler(Thing thing, Gson gson, StateDescriptionProvider stateDescriptionProvider, - CommandDescriptionProvider commandDescriptionProvider) { + public LightThingHandler(Thing thing, Gson gson, DeconzDynamicStateDescriptionProvider stateDescriptionProvider, + DeconzDynamicCommandDescriptionProvider commandDescriptionProvider) { super(thing, gson, ResourceType.LIGHTS); this.stateDescriptionProvider = stateDescriptionProvider; this.commandDescriptionProvider = commandDescriptionProvider; @@ -123,15 +122,11 @@ public class LightThingHandler extends DeconzBaseThingHandler { ctMin = ctMinString == null ? ZCL_CT_MIN : Integer.parseInt(ctMinString); // minimum and maximum are inverted due to mired/kelvin conversion! - StateDescription stateDescription = StateDescriptionFragmentBuilder.create() + StateDescriptionFragment stateDescriptionFragment = StateDescriptionFragmentBuilder.create() .withMinimum(new BigDecimal(miredToKelvin(ctMax))) - .withMaximum(new BigDecimal(miredToKelvin(ctMin))).build().toStateDescription(); - if (stateDescription != null) { - stateDescriptionProvider.setDescription(new ChannelUID(thing.getUID(), CHANNEL_COLOR_TEMPERATURE), - stateDescription); - } else { - logger.warn("Failed to create state description in thing {}", thing.getUID()); - } + .withMaximum(new BigDecimal(miredToKelvin(ctMin))).build(); + stateDescriptionProvider.setDescriptionFragment( + new ChannelUID(thing.getUID(), CHANNEL_COLOR_TEMPERATURE), stateDescriptionFragment); } catch (NumberFormatException e) { needsPropertyUpdate = true; } @@ -370,20 +365,16 @@ public class LightThingHandler extends DeconzBaseThingHandler { List 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()); + commandDescriptionProvider.setCommandOptions(effectChannelUID, toCommandOptionList(options)); break; case TINT_MUELLER: options = List.of("none", "colorloop", "sunset", "party", "worklight", "campfire", "romance", "nightlight"); - commandDescriptionProvider.setDescription(effectChannelUID, - CommandDescriptionBuilder.create().withCommandOptions(toCommandOptionList(options)).build()); + commandDescriptionProvider.setCommandOptions(effectChannelUID, toCommandOptionList(options)); break; default: options = List.of("none", "colorloop"); - commandDescriptionProvider.setDescription(effectChannelUID, - CommandDescriptionBuilder.create().withCommandOptions(toCommandOptionList(options)).build()); - + commandDescriptionProvider.setCommandOptions(effectChannelUID, toCommandOptionList(options)); } } diff --git a/bundles/org.openhab.binding.deconz/src/test/java/org/openhab/binding/deconz/LightsTest.java b/bundles/org.openhab.binding.deconz/src/test/java/org/openhab/binding/deconz/LightsTest.java index 2e90d778ce3..b58a8335fff 100644 --- a/bundles/org.openhab.binding.deconz/src/test/java/org/openhab/binding/deconz/LightsTest.java +++ b/bundles/org.openhab.binding.deconz/src/test/java/org/openhab/binding/deconz/LightsTest.java @@ -28,8 +28,8 @@ 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.DeconzDynamicCommandDescriptionProvider; +import org.openhab.binding.deconz.internal.DeconzDynamicStateDescriptionProvider; import org.openhab.binding.deconz.internal.dto.LightMessage; import org.openhab.binding.deconz.internal.handler.LightThingHandler; import org.openhab.binding.deconz.internal.types.LightType; @@ -60,8 +60,8 @@ public class LightsTest { private @NonNullByDefault({}) Gson gson; private @Mock @NonNullByDefault({}) ThingHandlerCallback thingHandlerCallback; - private @Mock @NonNullByDefault({}) StateDescriptionProvider stateDescriptionProvider; - private @Mock @NonNullByDefault({}) CommandDescriptionProvider commandDescriptionProvider; + private @Mock @NonNullByDefault({}) DeconzDynamicStateDescriptionProvider stateDescriptionProvider; + private @Mock @NonNullByDefault({}) DeconzDynamicCommandDescriptionProvider commandDescriptionProvider; @BeforeEach public void initialize() { @@ -116,7 +116,7 @@ public class LightsTest { lightThingHandler.initialize(); - Mockito.verify(stateDescriptionProvider).setDescription(eq(channelUID_ct), any()); + Mockito.verify(stateDescriptionProvider).setDescriptionFragment(eq(channelUID_ct), any()); } @Test