From 79c60f119513e05c661fb934e899c0456bce729c Mon Sep 17 00:00:00 2001 From: Henning Treu Date: Wed, 6 Feb 2019 10:20:28 +0100 Subject: [PATCH] Extend ChannelType by command options This addresses https://github.com/eclipse/smarthome/issues/5099 by adding a command description with command options along with the state description. Command options will give a hint to UIs about the specific commands a channel provides. Command options could be rendered as a drop down and also represent the current state or rendered as push buttons to simply send a command to the ThingHandler. The infrstructure basically copies the StateDescription infrastructure with CommandDescriptionProviders and an `DynamicCommandDescriptionProvider` interface for bindings to hook in and provide dynamic command options. Signed-off-by: Henning Treu --- .../internal/channel/ChannelTypeResource.java | 3 +- .../rest/core/item/EnrichedGroupItemDTO.java | 2 +- .../io/rest/core/item/EnrichedItemDTO.java | 5 +- .../rest/core/item/EnrichedItemDTOMapper.java | 3 +- .../resources/ESH-INF/thing/channel-types.xml | 5 +- .../xml/internal/ChannelTypeConverter.java | 24 +++- .../internal/CommandDescriptionConverter.java | 76 ++++++++++ .../xml/internal/ThingDescriptionReader.java | 4 + .../thing-description-1.0.0.xsd | 7 + .../core/thing/dto/ChannelTypeDTO.java | 6 +- .../ChannelTypeI18nLocalizationService.java | 26 +++- .../core/thing/i18n/ThingTypeI18nUtil.java | 7 + .../ChannelCommandDescriptionProvider.java | 135 ++++++++++++++++++ .../type/StateChannelTypeBuilderImpl.java | 17 ++- .../core/thing/type/ChannelType.java | 47 +++++- .../DynamicCommandDescriptionProvider.java | 48 +++++++ .../thing/type/StateChannelTypeBuilder.java | 11 ++ .../core/internal/items/ItemRegistryImpl.java | 20 +++ .../CommandDescriptionServiceImpl.java | 67 +++++++++ .../types/CommandDescriptionImpl.java | 51 +++++++ .../smarthome/core/items/GenericItem.java | 36 +++++ .../eclipse/smarthome/core/items/Item.java | 24 ++++ .../service/CommandDescriptionService.java | 39 +++++ .../core/types/CommandDescription.java | 34 +++++ .../core/types/CommandDescriptionBuilder.java | 49 +++++++ .../types/CommandDescriptionProvider.java | 38 +++++ .../smarthome/core/types/CommandOption.java | 52 +++++++ .../test/hue/TestHueChannelTypeProvider.java | 29 ++-- .../smarthome/core/items/GenericItemTest.java | 62 ++++++++ .../core/items/ItemRegistryImplTest.java | 25 ++++ ...annelStateDescriptionProviderOSGiTest.java | 8 +- .../thing/xml/test/ChannelTypesI18nTest.java | 4 + .../ESH-INF/i18n/somebinding.properties | 3 + .../resources/ESH-INF/thing/thing-types.xml | 12 +- 34 files changed, 949 insertions(+), 30 deletions(-) create mode 100644 bundles/org.openhab.core.thing.xml/src/main/java/org/eclipse/smarthome/core/thing/xml/internal/CommandDescriptionConverter.java create mode 100644 bundles/org.openhab.core.thing/src/main/java/org/eclipse/smarthome/core/thing/internal/ChannelCommandDescriptionProvider.java create mode 100644 bundles/org.openhab.core.thing/src/main/java/org/eclipse/smarthome/core/thing/type/DynamicCommandDescriptionProvider.java create mode 100644 bundles/org.openhab.core/src/main/java/org/eclipse/smarthome/core/internal/service/CommandDescriptionServiceImpl.java create mode 100644 bundles/org.openhab.core/src/main/java/org/eclipse/smarthome/core/internal/types/CommandDescriptionImpl.java create mode 100644 bundles/org.openhab.core/src/main/java/org/eclipse/smarthome/core/service/CommandDescriptionService.java create mode 100644 bundles/org.openhab.core/src/main/java/org/eclipse/smarthome/core/types/CommandDescription.java create mode 100644 bundles/org.openhab.core/src/main/java/org/eclipse/smarthome/core/types/CommandDescriptionBuilder.java create mode 100644 bundles/org.openhab.core/src/main/java/org/eclipse/smarthome/core/types/CommandDescriptionProvider.java create mode 100644 bundles/org.openhab.core/src/main/java/org/eclipse/smarthome/core/types/CommandOption.java diff --git a/bundles/org.openhab.core.io.rest.core/src/main/java/org/eclipse/smarthome/io/rest/core/internal/channel/ChannelTypeResource.java b/bundles/org.openhab.core.io.rest.core/src/main/java/org/eclipse/smarthome/io/rest/core/internal/channel/ChannelTypeResource.java index 47383a93f2c..25dc7803ddf 100644 --- a/bundles/org.openhab.core.io.rest.core/src/main/java/org/eclipse/smarthome/io/rest/core/internal/channel/ChannelTypeResource.java +++ b/bundles/org.openhab.core.io.rest.core/src/main/java/org/eclipse/smarthome/io/rest/core/internal/channel/ChannelTypeResource.java @@ -207,7 +207,8 @@ private ChannelTypeDTO convertToChannelTypeDTO(ChannelType channelType, Locale l return new ChannelTypeDTO(channelType.getUID().toString(), channelType.getLabel(), channelType.getDescription(), channelType.getCategory(), channelType.getItemType(), channelType.getKind(), parameters, - parameterGroups, channelType.getState(), channelType.getTags(), channelType.isAdvanced()); + parameterGroups, channelType.getState(), channelType.getTags(), channelType.isAdvanced(), + channelType.getCommandDescription()); } @Override diff --git a/bundles/org.openhab.core.io.rest.core/src/main/java/org/eclipse/smarthome/io/rest/core/item/EnrichedGroupItemDTO.java b/bundles/org.openhab.core.io.rest.core/src/main/java/org/eclipse/smarthome/io/rest/core/item/EnrichedGroupItemDTO.java index 6cfdc8c4d0f..5dabc354fb4 100644 --- a/bundles/org.openhab.core.io.rest.core/src/main/java/org/eclipse/smarthome/io/rest/core/item/EnrichedGroupItemDTO.java +++ b/bundles/org.openhab.core.io.rest.core/src/main/java/org/eclipse/smarthome/io/rest/core/item/EnrichedGroupItemDTO.java @@ -27,7 +27,7 @@ public class EnrichedGroupItemDTO extends EnrichedItemDTO { public EnrichedGroupItemDTO(ItemDTO itemDTO, EnrichedItemDTO[] members, String link, String state, String transformedState, StateDescription stateDescription) { - super(itemDTO, link, state, transformedState, stateDescription); + super(itemDTO, link, state, transformedState, stateDescription, null); this.members = members; this.groupType = ((GroupItemDTO) itemDTO).groupType; this.function = ((GroupItemDTO) itemDTO).function; diff --git a/bundles/org.openhab.core.io.rest.core/src/main/java/org/eclipse/smarthome/io/rest/core/item/EnrichedItemDTO.java b/bundles/org.openhab.core.io.rest.core/src/main/java/org/eclipse/smarthome/io/rest/core/item/EnrichedItemDTO.java index d397fd71642..50e06298193 100644 --- a/bundles/org.openhab.core.io.rest.core/src/main/java/org/eclipse/smarthome/io/rest/core/item/EnrichedItemDTO.java +++ b/bundles/org.openhab.core.io.rest.core/src/main/java/org/eclipse/smarthome/io/rest/core/item/EnrichedItemDTO.java @@ -15,6 +15,7 @@ import java.util.Map; import org.eclipse.smarthome.core.items.dto.ItemDTO; +import org.eclipse.smarthome.core.types.CommandDescription; import org.eclipse.smarthome.core.types.StateDescription; /** @@ -31,11 +32,12 @@ public class EnrichedItemDTO extends ItemDTO { public String state; public String transformedState; public StateDescription stateDescription; + public CommandDescription commandDescription; public Map metadata; public boolean editable; public EnrichedItemDTO(ItemDTO itemDTO, String link, String state, String transformedState, - StateDescription stateDescription) { + StateDescription stateDescription, CommandDescription commandDescription) { this.type = itemDTO.type; this.name = itemDTO.name; this.label = itemDTO.label; @@ -46,6 +48,7 @@ public EnrichedItemDTO(ItemDTO itemDTO, String link, String state, String transf this.state = state; this.transformedState = transformedState; this.stateDescription = stateDescription; + this.commandDescription = commandDescription; } } diff --git a/bundles/org.openhab.core.io.rest.core/src/main/java/org/eclipse/smarthome/io/rest/core/item/EnrichedItemDTOMapper.java b/bundles/org.openhab.core.io.rest.core/src/main/java/org/eclipse/smarthome/io/rest/core/item/EnrichedItemDTOMapper.java index d8174bb9be7..933d438e3a9 100644 --- a/bundles/org.openhab.core.io.rest.core/src/main/java/org/eclipse/smarthome/io/rest/core/item/EnrichedItemDTOMapper.java +++ b/bundles/org.openhab.core.io.rest.core/src/main/java/org/eclipse/smarthome/io/rest/core/item/EnrichedItemDTOMapper.java @@ -85,7 +85,8 @@ private static EnrichedItemDTO map(Item item, ItemDTO itemDTO, URI uri, boolean enrichedItemDTO = new EnrichedGroupItemDTO(itemDTO, memberDTOs, link, state, transformedState, stateDescription); } else { - enrichedItemDTO = new EnrichedItemDTO(itemDTO, link, state, transformedState, stateDescription); + enrichedItemDTO = new EnrichedItemDTO(itemDTO, link, state, transformedState, stateDescription, + item.getCommandDescription(locale)); } return enrichedItemDTO; diff --git a/bundles/org.openhab.core.test.magic/src/main/resources/ESH-INF/thing/channel-types.xml b/bundles/org.openhab.core.test.magic/src/main/resources/ESH-INF/thing/channel-types.xml index 8214a4f436f..7c862b2be8d 100644 --- a/bundles/org.openhab.core.test.magic/src/main/resources/ESH-INF/thing/channel-types.xml +++ b/bundles/org.openhab.core.test.magic/src/main/resources/ESH-INF/thing/channel-types.xml @@ -55,13 +55,14 @@ String The alert channel allows a temporary change to the bulb’s state. - + - + + recommend diff --git a/bundles/org.openhab.core.thing.xml/src/main/java/org/eclipse/smarthome/core/thing/xml/internal/ChannelTypeConverter.java b/bundles/org.openhab.core.thing.xml/src/main/java/org/eclipse/smarthome/core/thing/xml/internal/ChannelTypeConverter.java index 9fc3ec6dee0..b2dc25bcac9 100644 --- a/bundles/org.openhab.core.thing.xml/src/main/java/org/eclipse/smarthome/core/thing/xml/internal/ChannelTypeConverter.java +++ b/bundles/org.openhab.core.thing.xml/src/main/java/org/eclipse/smarthome/core/thing/xml/internal/ChannelTypeConverter.java @@ -28,6 +28,8 @@ import org.eclipse.smarthome.core.thing.type.ChannelType; import org.eclipse.smarthome.core.thing.type.ChannelTypeBuilder; import org.eclipse.smarthome.core.thing.type.ChannelTypeUID; +import org.eclipse.smarthome.core.thing.type.StateChannelTypeBuilder; +import org.eclipse.smarthome.core.types.CommandDescription; import org.eclipse.smarthome.core.types.EventDescription; import org.eclipse.smarthome.core.types.StateDescription; @@ -140,6 +142,20 @@ private EventDescription readEventDescription(NodeIterator nodeIterator) { return null; } + private CommandDescription readCommandDescription(NodeIterator nodeIterator) throws ConversionException { + Object nextNode = nodeIterator.next(); + + if (nextNode != null) { + if (nextNode instanceof CommandDescription) { + return (CommandDescription) nextNode; + } + + nodeIterator.revert(); + } + + return null; + } + @Override protected ChannelTypeXmlResult unmarshalType(HierarchicalStreamReader reader, UnmarshallingContext context, Map attributes, NodeIterator nodeIterator) throws ConversionException { @@ -157,6 +173,7 @@ protected ChannelTypeXmlResult unmarshalType(HierarchicalStreamReader reader, Un Set tags = readTags(nodeIterator); StateDescription stateDescription = readStateDescription(nodeIterator); + CommandDescription commandDescription = readCommandDescription(nodeIterator); EventDescription eventDescription = readEventDescription(nodeIterator); AutoUpdatePolicy autoUpdatePolicy = readAutoUpdatePolicy(nodeIterator); @@ -172,10 +189,11 @@ protected ChannelTypeXmlResult unmarshalType(HierarchicalStreamReader reader, Un URI configDescriptionURI = (URI) configDescriptionObjects[0]; ChannelType channelType = null; if (cKind == ChannelKind.STATE) { - channelType = ChannelTypeBuilder.state(channelTypeUID, label, itemType).isAdvanced(advanced) - .withDescription(description).withCategory(category).withTags(tags) + StateChannelTypeBuilder builder = ChannelTypeBuilder.state(channelTypeUID, label, itemType) + .isAdvanced(advanced).withDescription(description).withCategory(category).withTags(tags) .withConfigDescriptionURI(configDescriptionURI).withStateDescription(stateDescription) - .withAutoUpdatePolicy(autoUpdatePolicy).build(); + .withAutoUpdatePolicy(autoUpdatePolicy).withCommandDescription(commandDescription); + channelType = builder.build(); } else if (cKind == ChannelKind.TRIGGER) { channelType = ChannelTypeBuilder.trigger(channelTypeUID, label).isAdvanced(advanced) .withDescription(description).withCategory(category).withTags(tags) diff --git a/bundles/org.openhab.core.thing.xml/src/main/java/org/eclipse/smarthome/core/thing/xml/internal/CommandDescriptionConverter.java b/bundles/org.openhab.core.thing.xml/src/main/java/org/eclipse/smarthome/core/thing/xml/internal/CommandDescriptionConverter.java new file mode 100644 index 00000000000..8678d0503bd --- /dev/null +++ b/bundles/org.openhab.core.thing.xml/src/main/java/org/eclipse/smarthome/core/thing/xml/internal/CommandDescriptionConverter.java @@ -0,0 +1,76 @@ +/** + * Copyright (c) 2014,2019 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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.eclipse.smarthome.core.thing.xml.internal; + +import org.eclipse.smarthome.config.xml.util.GenericUnmarshaller; +import org.eclipse.smarthome.config.xml.util.NodeIterator; +import org.eclipse.smarthome.config.xml.util.NodeList; +import org.eclipse.smarthome.config.xml.util.NodeValue; +import org.eclipse.smarthome.core.types.CommandDescription; +import org.eclipse.smarthome.core.types.CommandDescriptionBuilder; +import org.eclipse.smarthome.core.types.CommandOption; + +import com.thoughtworks.xstream.converters.ConversionException; +import com.thoughtworks.xstream.converters.Converter; +import com.thoughtworks.xstream.converters.UnmarshallingContext; +import com.thoughtworks.xstream.io.HierarchicalStreamReader; + +/** + * The {@link CommandDescriptionConverter} is a concrete implementation of the {@code XStream} {@link Converter} + * interface used to convert a command description within an XML document into a {@link CommandDescription} object. + *

+ * This converter converts {@code command} XML tags. + * + * @author Henning Treu - Initial Contribution + */ +public class CommandDescriptionConverter extends GenericUnmarshaller { + + public CommandDescriptionConverter() { + super(CommandDescription.class); + } + + @Override + public final CommandDescription unmarshal(HierarchicalStreamReader reader, UnmarshallingContext context) { + NodeList nodes = (NodeList) context.convertAnother(context, NodeList.class); + NodeIterator nodeIterator = new NodeIterator(nodes.getList()); + + NodeList commandOptionsNode = (NodeList) nodeIterator.next(); + if (commandOptionsNode != null) { + if ("options".equals(commandOptionsNode.getNodeName())) { + + CommandDescriptionBuilder commandDescriptionBuilder = CommandDescriptionBuilder.create(); + for (Object coNodeObject : commandOptionsNode.getList()) { + NodeValue optionsNode = (NodeValue) coNodeObject; + + if ("option".equals(optionsNode.getNodeName())) { + String name = (String) optionsNode.getValue(); + String command = optionsNode.getAttributes().get("value"); + + if (name != null && command != null) { + commandDescriptionBuilder.withCommandOption(new CommandOption(command, name)); + } + } else { + throw new ConversionException("The 'options' node must only contain 'option' nodes!"); + } + } + + nodeIterator.assertEndOfType(); + return commandDescriptionBuilder.build(); + } + } + + nodeIterator.assertEndOfType(); + return null; + } + +} diff --git a/bundles/org.openhab.core.thing.xml/src/main/java/org/eclipse/smarthome/core/thing/xml/internal/ThingDescriptionReader.java b/bundles/org.openhab.core.thing.xml/src/main/java/org/eclipse/smarthome/core/thing/xml/internal/ThingDescriptionReader.java index 709f4343f1d..0ff512646df 100644 --- a/bundles/org.openhab.core.thing.xml/src/main/java/org/eclipse/smarthome/core/thing/xml/internal/ThingDescriptionReader.java +++ b/bundles/org.openhab.core.thing.xml/src/main/java/org/eclipse/smarthome/core/thing/xml/internal/ThingDescriptionReader.java @@ -29,6 +29,7 @@ import org.eclipse.smarthome.config.xml.util.NodeValue; import org.eclipse.smarthome.config.xml.util.NodeValueConverter; import org.eclipse.smarthome.config.xml.util.XmlDocumentReader; +import org.eclipse.smarthome.core.types.CommandDescription; import org.eclipse.smarthome.core.types.EventDescription; import org.eclipse.smarthome.core.types.StateDescription; @@ -68,6 +69,7 @@ public void registerConverters(XStream xstream) { xstream.registerConverter(new ChannelTypeConverter()); xstream.registerConverter(new ChannelGroupTypeConverter()); xstream.registerConverter(new StateDescriptionConverter()); + xstream.registerConverter(new CommandDescriptionConverter()); xstream.registerConverter(new EventDescriptionConverter()); xstream.registerConverter(new ConfigDescriptionConverter()); xstream.registerConverter(new ConfigDescriptionParameterConverter()); @@ -97,6 +99,7 @@ public void registerAliases(XStream xstream) { xstream.alias("tags", NodeList.class); xstream.alias("tag", NodeValue.class); xstream.alias("state", StateDescription.class); + xstream.alias("command", CommandDescription.class); xstream.alias("event", EventDescription.class); xstream.alias("options", NodeList.class); xstream.alias("option", NodeValue.class); @@ -110,6 +113,7 @@ public void registerAliases(XStream xstream) { xstream.alias("properties", NodeList.class); xstream.alias("property", NodeValue.class); xstream.alias("representation-property", NodeValue.class); + xstream.alias("command-options", NodeList.class); xstream.alias("autoUpdatePolicy", NodeValue.class); } diff --git a/bundles/org.openhab.core.thing.xml/thing-description-1.0.0.xsd b/bundles/org.openhab.core.thing.xml/thing-description-1.0.0.xsd index 8ce02fd4b01..d8ee2f94eab 100644 --- a/bundles/org.openhab.core.thing.xml/thing-description-1.0.0.xsd +++ b/bundles/org.openhab.core.thing.xml/thing-description-1.0.0.xsd @@ -70,6 +70,7 @@ + @@ -170,6 +171,12 @@ + + + + + + diff --git a/bundles/org.openhab.core.thing/src/main/java/org/eclipse/smarthome/core/thing/dto/ChannelTypeDTO.java b/bundles/org.openhab.core.thing/src/main/java/org/eclipse/smarthome/core/thing/dto/ChannelTypeDTO.java index 745804bf5db..cc2147df521 100644 --- a/bundles/org.openhab.core.thing/src/main/java/org/eclipse/smarthome/core/thing/dto/ChannelTypeDTO.java +++ b/bundles/org.openhab.core.thing/src/main/java/org/eclipse/smarthome/core/thing/dto/ChannelTypeDTO.java @@ -14,9 +14,11 @@ import java.util.List; import java.util.Set; + import org.eclipse.smarthome.config.core.dto.ConfigDescriptionParameterDTO; import org.eclipse.smarthome.config.core.dto.ConfigDescriptionParameterGroupDTO; import org.eclipse.smarthome.core.thing.type.ChannelKind; +import org.eclipse.smarthome.core.types.CommandDescription; import org.eclipse.smarthome.core.types.StateDescription; /** @@ -38,6 +40,7 @@ public class ChannelTypeDTO { public Set tags; public String UID; public boolean advanced; + public CommandDescription commandDescription; public ChannelTypeDTO() { } @@ -45,7 +48,7 @@ public ChannelTypeDTO() { public ChannelTypeDTO(String UID, String label, String description, String category, String itemType, ChannelKind kind, List parameters, List parameterGroups, StateDescription stateDescription, - Set tags, boolean advanced) { + Set tags, boolean advanced, CommandDescription commandDescription) { this.UID = UID; this.label = label; this.description = description; @@ -57,5 +60,6 @@ public ChannelTypeDTO(String UID, String label, String description, String categ this.kind = kind.toString(); this.itemType = itemType; this.advanced = advanced; + this.commandDescription = commandDescription; } } diff --git a/bundles/org.openhab.core.thing/src/main/java/org/eclipse/smarthome/core/thing/i18n/ChannelTypeI18nLocalizationService.java b/bundles/org.openhab.core.thing/src/main/java/org/eclipse/smarthome/core/thing/i18n/ChannelTypeI18nLocalizationService.java index 4d443963a1f..dfc57f975ec 100644 --- a/bundles/org.openhab.core.thing/src/main/java/org/eclipse/smarthome/core/thing/i18n/ChannelTypeI18nLocalizationService.java +++ b/bundles/org.openhab.core.thing/src/main/java/org/eclipse/smarthome/core/thing/i18n/ChannelTypeI18nLocalizationService.java @@ -24,6 +24,9 @@ import org.eclipse.smarthome.core.thing.type.ChannelTypeUID; import org.eclipse.smarthome.core.thing.type.StateChannelTypeBuilder; import org.eclipse.smarthome.core.thing.type.TriggerChannelTypeBuilder; +import org.eclipse.smarthome.core.types.CommandDescription; +import org.eclipse.smarthome.core.types.CommandDescriptionBuilder; +import org.eclipse.smarthome.core.types.CommandOption; import org.eclipse.smarthome.core.types.StateDescription; import org.eclipse.smarthome.core.types.StateOption; import org.osgi.framework.Bundle; @@ -74,6 +77,24 @@ protected void unsetTranslationProvider(TranslationProvider i18nProvider) { state.isReadOnly(), localizedOptions); } + private @Nullable CommandDescription createLocalizedCommandDescription(final Bundle bundle, + final @Nullable CommandDescription command, final ChannelTypeUID channelTypeUID, + final @Nullable Locale locale) { + if (command == null) { + return null; + } + + CommandDescriptionBuilder commandDescriptionBuilder = CommandDescriptionBuilder.create(); + for (final CommandOption options : command.getCommandOptions()) { + String optionLabel = thingTypeI18nUtil.getChannelCommandOption(bundle, channelTypeUID, options.getCommand(), + options.getLabel(), locale); + optionLabel = optionLabel == null ? "" : optionLabel; + commandDescriptionBuilder.withCommandOption(new CommandOption(options.getCommand(), optionLabel)); + } + + return commandDescriptionBuilder.build(); + } + public ChannelType createLocalizedChannelType(Bundle bundle, ChannelType channelType, @Nullable Locale locale) { ChannelTypeUID channelTypeUID = channelType.getUID(); String defaultLabel = channelType.getLabel(); @@ -85,12 +106,15 @@ public ChannelType createLocalizedChannelType(Bundle bundle, ChannelType channel case STATE: StateDescription state = createLocalizedStateDescription(bundle, channelType.getState(), channelTypeUID, locale); + CommandDescription command = createLocalizedCommandDescription(bundle, + channelType.getCommandDescription(), channelTypeUID, locale); StateChannelTypeBuilder stateBuilder = ChannelTypeBuilder .state(channelTypeUID, label == null ? defaultLabel : label, channelType.getItemType()) .isAdvanced(channelType.isAdvanced()).withCategory(channelType.getCategory()) .withConfigDescriptionURI(channelType.getConfigDescriptionURI()).withTags(channelType.getTags()) - .withStateDescription(state).withAutoUpdatePolicy(channelType.getAutoUpdatePolicy()); + .withStateDescription(state).withAutoUpdatePolicy(channelType.getAutoUpdatePolicy()) + .withCommandDescription(command); if (description != null) { stateBuilder.withDescription(description); } diff --git a/bundles/org.openhab.core.thing/src/main/java/org/eclipse/smarthome/core/thing/i18n/ThingTypeI18nUtil.java b/bundles/org.openhab.core.thing/src/main/java/org/eclipse/smarthome/core/thing/i18n/ThingTypeI18nUtil.java index 558e8103926..ad9fa73e828 100644 --- a/bundles/org.openhab.core.thing/src/main/java/org/eclipse/smarthome/core/thing/i18n/ThingTypeI18nUtil.java +++ b/bundles/org.openhab.core.thing/src/main/java/org/eclipse/smarthome/core/thing/i18n/ThingTypeI18nUtil.java @@ -116,6 +116,13 @@ public ThingTypeI18nUtil(TranslationProvider i18nProvider) { return i18nProvider.getText(bundle, key, defaultOptionLabel, locale); } + public @Nullable String getChannelCommandOption(Bundle bundle, ChannelTypeUID channelTypeUID, String optionValue, + String defaultOptionLabel, @Nullable Locale locale) { + String key = I18nUtil.stripConstantOr(defaultOptionLabel, + () -> inferChannelKey(channelTypeUID, "command.option." + optionValue)); + return i18nProvider.getText(bundle, key, defaultOptionLabel, locale); + } + public @Nullable String getChannelStatePattern(Bundle bundle, ChannelTypeUID channelTypeUID, String defaultPattern, @Nullable Locale locale) { String key = I18nUtil.stripConstantOr(defaultPattern, () -> inferChannelKey(channelTypeUID, "state.pattern")); diff --git a/bundles/org.openhab.core.thing/src/main/java/org/eclipse/smarthome/core/thing/internal/ChannelCommandDescriptionProvider.java b/bundles/org.openhab.core.thing/src/main/java/org/eclipse/smarthome/core/thing/internal/ChannelCommandDescriptionProvider.java new file mode 100644 index 00000000000..d7afbd7b72c --- /dev/null +++ b/bundles/org.openhab.core.thing/src/main/java/org/eclipse/smarthome/core/thing/internal/ChannelCommandDescriptionProvider.java @@ -0,0 +1,135 @@ +/** + * Copyright (c) 2014,2019 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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.eclipse.smarthome.core.thing.internal; + +import java.util.List; +import java.util.Locale; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArrayList; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.smarthome.core.thing.Channel; +import org.eclipse.smarthome.core.thing.ChannelUID; +import org.eclipse.smarthome.core.thing.ThingRegistry; +import org.eclipse.smarthome.core.thing.link.ItemChannelLinkRegistry; +import org.eclipse.smarthome.core.thing.type.ChannelType; +import org.eclipse.smarthome.core.thing.type.DynamicCommandDescriptionProvider; +import org.eclipse.smarthome.core.thing.type.ThingTypeRegistry; +import org.eclipse.smarthome.core.types.CommandDescription; +import org.eclipse.smarthome.core.types.CommandDescriptionProvider; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; +import org.osgi.service.component.annotations.ReferencePolicy; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Provides the {@link ChannelType} specific {@link CommandDescription} for the given item name and locale. + * + * @author Henning Treu - Initial contribution + * + */ +@NonNullByDefault +@Component(immediate = true) +public class ChannelCommandDescriptionProvider implements CommandDescriptionProvider { + + private final Logger logger = LoggerFactory.getLogger(ChannelCommandDescriptionProvider.class); + + private @NonNullByDefault({}) ItemChannelLinkRegistry itemChannelLinkRegistry; + private @NonNullByDefault({}) ThingTypeRegistry thingTypeRegistry; + private @NonNullByDefault({}) ThingRegistry thingRegistry; + + private final List dynamicCommandDescriptionProviders = new CopyOnWriteArrayList<>(); + + @Override + public @Nullable CommandDescription getCommandDescription(String itemName, @Nullable Locale locale) { + Set boundChannels = itemChannelLinkRegistry.getBoundChannels(itemName); + if (!boundChannels.isEmpty()) { + ChannelUID channelUID = boundChannels.iterator().next(); + Channel channel = thingRegistry.getChannel(channelUID); + if (channel != null) { + CommandDescription commandDescription = null; + ChannelType channelType = thingTypeRegistry.getChannelType(channel, locale); + if (channelType != null) { + commandDescription = channelType.getCommandDescription(); + } + CommandDescription dynamicCommandDescription = getDynamicCommandDescription(channel, commandDescription, + locale); + if (dynamicCommandDescription != null) { + return dynamicCommandDescription; + } + return commandDescription; + } + } + + return null; + } + + private @Nullable CommandDescription getDynamicCommandDescription(Channel channel, + @Nullable CommandDescription originalCommandDescription, @Nullable Locale locale) { + for (DynamicCommandDescriptionProvider dynamicCommandDescriptionProvider : dynamicCommandDescriptionProviders) { + try { + CommandDescription dynamicCommandDescription = dynamicCommandDescriptionProvider + .getCommandDescription(channel, originalCommandDescription, locale); + if (dynamicCommandDescription != null) { + return dynamicCommandDescription; + } + } catch (Exception e) { + logger.error("Error evaluating {}#getCommandDescription: {}", + dynamicCommandDescriptionProvider.getClass(), e.getLocalizedMessage(), e); + } + } + + return null; + } + + @Reference + protected void setThingTypeRegistry(ThingTypeRegistry thingTypeRegistry) { + this.thingTypeRegistry = thingTypeRegistry; + } + + protected void unsetThingTypeRegistry(ThingTypeRegistry thingTypeRegistry) { + this.thingTypeRegistry = null; + } + + @Reference + protected void setItemChannelLinkRegistry(ItemChannelLinkRegistry itemChannelLinkRegistry) { + this.itemChannelLinkRegistry = itemChannelLinkRegistry; + } + + protected void unsetItemChannelLinkRegistry(ItemChannelLinkRegistry itemChannelLinkRegistry) { + this.itemChannelLinkRegistry = null; + } + + @Reference + protected void setThingRegistry(ThingRegistry thingRegistry) { + this.thingRegistry = thingRegistry; + } + + protected void unsetThingRegistry(ThingRegistry thingRegistry) { + this.thingRegistry = null; + } + + @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) + protected void addDynamicCommandDescriptionProvider( + DynamicCommandDescriptionProvider dynamicCommandDescriptionProvider) { + this.dynamicCommandDescriptionProviders.add(dynamicCommandDescriptionProvider); + } + + protected void removeDynamicCommandDescriptionProvider( + DynamicCommandDescriptionProvider dynamicCommandDescriptionProvider) { + this.dynamicCommandDescriptionProviders.remove(dynamicCommandDescriptionProvider); + } +} diff --git a/bundles/org.openhab.core.thing/src/main/java/org/eclipse/smarthome/core/thing/internal/type/StateChannelTypeBuilderImpl.java b/bundles/org.openhab.core.thing/src/main/java/org/eclipse/smarthome/core/thing/internal/type/StateChannelTypeBuilderImpl.java index c11f73669e1..629893c37c5 100644 --- a/bundles/org.openhab.core.thing/src/main/java/org/eclipse/smarthome/core/thing/internal/type/StateChannelTypeBuilderImpl.java +++ b/bundles/org.openhab.core.thing/src/main/java/org/eclipse/smarthome/core/thing/internal/type/StateChannelTypeBuilderImpl.java @@ -20,6 +20,7 @@ import org.eclipse.smarthome.core.thing.type.ChannelType; import org.eclipse.smarthome.core.thing.type.ChannelTypeUID; import org.eclipse.smarthome.core.thing.type.StateChannelTypeBuilder; +import org.eclipse.smarthome.core.types.CommandDescription; import org.eclipse.smarthome.core.types.StateDescription; /** @@ -35,6 +36,7 @@ public class StateChannelTypeBuilderImpl extends AbstractChannelTypeBuilderHint: This class is immutable. * * @author Michael Grammling - Initial Contribution + * @author Henning Treu - add command options */ public class ChannelType extends AbstractDescriptionType { @@ -40,6 +42,7 @@ public class ChannelType extends AbstractDescriptionType { private final Set tags; private final String category; private final StateDescription state; + private final CommandDescription commandDescription; private final EventDescription event; private final URI configDescriptionURI; private final AutoUpdatePolicy autoUpdatePolicy; @@ -65,6 +68,35 @@ public ChannelType(ChannelTypeUID uid, boolean advanced, String itemType, Channe null); } + /** + * Creates a new instance of a "write-only" {@link ChannelType} with command options. The purpose of this + * {@link ChannelType} is to send command to a device without updating the state of the corresponding channel. + * E.g. activate a special device mode which is not represented as a definitive state. + * + * @param uid the unique identifier which identifies this Channel type within + * the overall system (must neither be null, nor empty) + * @param advanced true if this channel type contains advanced features, otherwise false + * @param itemType the item type of this Channel type, e.g. {@code ColorItem} (must neither be null nor empty) + * @param label the human readable label for the according type + * (must neither be null nor empty) + * @param description the human readable description for the according type + * (could be null or empty) + * @param category the category of this Channel type, e.g. {@code TEMPERATURE} (could be null or empty) + * @param tags all tags of this {@link ChannelType}, e.g. {@code Alarm} (could be null or empty) + * @param commandDescription a {@link CommandDescription} which should be rendered as push-buttons. The command + * values will be send to the channel from this {@link ChannelType}. + * @param configDescriptionURI the link to the concrete ConfigDescription (could be null) + * @param autoUpdatePolicy the {@link AutoUpdatePolicy} to use. + * @throws IllegalArgumentException if the UID or the item type is null or empty, + * or the meta information is null + */ + public ChannelType(ChannelTypeUID uid, boolean advanced, String itemType, String label, String description, + String category, Set tags, CommandDescription commandDescription, URI configDescriptionURI, + AutoUpdatePolicy autoUpdatePolicy) { + this(uid, advanced, itemType, ChannelKind.STATE, label, description, category, tags, null, commandDescription, + null, configDescriptionURI, autoUpdatePolicy); + } + /** * Creates a new instance of this class with the specified parameters. * @@ -84,11 +116,19 @@ public ChannelType(ChannelTypeUID uid, boolean advanced, String itemType, Channe * @param configDescriptionURI the link to the concrete ConfigDescription (could be null) * @param autoUpdatePolicy the {@link AutoUpdatePolicy} to use. * @throws IllegalArgumentException if the UID or the item type is null or empty, - * or the the meta information is null + * or the meta information is null */ public ChannelType(ChannelTypeUID uid, boolean advanced, String itemType, ChannelKind kind, String label, String description, String category, Set tags, StateDescription state, EventDescription event, URI configDescriptionURI, AutoUpdatePolicy autoUpdatePolicy) throws IllegalArgumentException { + this(uid, advanced, itemType, kind, label, description, category, tags, state, null, event, + configDescriptionURI, autoUpdatePolicy); + } + + private ChannelType(ChannelTypeUID uid, boolean advanced, String itemType, ChannelKind kind, String label, + String description, String category, Set tags, StateDescription state, + CommandDescription commandDescription, EventDescription event, URI configDescriptionURI, + AutoUpdatePolicy autoUpdatePolicy) throws IllegalArgumentException { super(uid, label, description); if (kind == null) { @@ -115,6 +155,7 @@ public ChannelType(ChannelTypeUID uid, boolean advanced, String itemType, Channe this.advanced = advanced; this.category = category; this.state = state; + this.commandDescription = commandDescription; this.event = event; this.autoUpdatePolicy = autoUpdatePolicy; } @@ -215,4 +256,8 @@ public AutoUpdatePolicy getAutoUpdatePolicy() { return autoUpdatePolicy; } + public CommandDescription getCommandDescription() { + return commandDescription; + } + } diff --git a/bundles/org.openhab.core.thing/src/main/java/org/eclipse/smarthome/core/thing/type/DynamicCommandDescriptionProvider.java b/bundles/org.openhab.core.thing/src/main/java/org/eclipse/smarthome/core/thing/type/DynamicCommandDescriptionProvider.java new file mode 100644 index 00000000000..204829711f8 --- /dev/null +++ b/bundles/org.openhab.core.thing/src/main/java/org/eclipse/smarthome/core/thing/type/DynamicCommandDescriptionProvider.java @@ -0,0 +1,48 @@ +/** + * Copyright (c) 2014,2019 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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.eclipse.smarthome.core.thing.type; + +import java.util.Locale; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.smarthome.core.thing.Channel; +import org.eclipse.smarthome.core.types.CommandDescription; + +/** + * Implementations may provide channel specific {@link CommandDescription}s. + * + * @author Henning Treu - Initial contribution + * + */ +@NonNullByDefault +public interface DynamicCommandDescriptionProvider { + + /** + * For a given channel UID, return a {@link CommandDescription} that should be used for the channel, instead of the + * one defined statically in the {@link ChannelType}. + * + * For a particular channel, there should be only one provider of the dynamic command description. When more than + * one description is provided for the same channel (by different providers), only one will be used, from the + * provider that registered first. + * + * @param channel channel + * @param originalCommandDescription original command description retrieved from the channel type + * this is the description to be replaced by the provided one + * @param locale locale (can be null) + * @return command description or null if none provided + */ + @Nullable + CommandDescription getCommandDescription(Channel channel, @Nullable CommandDescription originalCommandDescription, + @Nullable Locale locale); +} diff --git a/bundles/org.openhab.core.thing/src/main/java/org/eclipse/smarthome/core/thing/type/StateChannelTypeBuilder.java b/bundles/org.openhab.core.thing/src/main/java/org/eclipse/smarthome/core/thing/type/StateChannelTypeBuilder.java index 16500fdd5ce..03c08dfd23c 100644 --- a/bundles/org.openhab.core.thing/src/main/java/org/eclipse/smarthome/core/thing/type/StateChannelTypeBuilder.java +++ b/bundles/org.openhab.core.thing/src/main/java/org/eclipse/smarthome/core/thing/type/StateChannelTypeBuilder.java @@ -14,6 +14,8 @@ import org.eclipse.jdt.annotation.NonNullByDefault; import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.smarthome.core.types.CommandDescription; +import org.eclipse.smarthome.core.types.CommandOption; import org.eclipse.smarthome.core.types.StateDescription; /** @@ -41,4 +43,13 @@ public interface StateChannelTypeBuilder extends ChannelTypeBuilder> registryHooks = new CopyOnWriteArrayList<>(); private StateDescriptionService stateDescriptionService; + private CommandDescriptionService commandDescriptionService; private MetadataRegistry metadataRegistry; private UnitProvider unitProvider; @@ -192,6 +194,7 @@ private void injectServices(Item item) { GenericItem genericItem = (GenericItem) item; genericItem.setEventPublisher(getEventPublisher()); genericItem.setStateDescriptionService(stateDescriptionService); + genericItem.setCommandDescriptionService(commandDescriptionService); genericItem.setUnitProvider(unitProvider); genericItem.setItemStateConverter(itemStateConverter); } @@ -451,6 +454,23 @@ protected void unsetStateDescriptionService(StateDescriptionService stateDescrip } } + @Reference(cardinality = ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC) + public void setCommandDescriptionService(CommandDescriptionService commandDescriptionService) { + this.commandDescriptionService = commandDescriptionService; + + for (Item item : getItems()) { + ((GenericItem) item).setCommandDescriptionService(commandDescriptionService); + } + } + + public void unsetCommandDescriptionService(CommandDescriptionService commandDescriptionService) { + this.commandDescriptionService = null; + + for (Item item : getItems()) { + ((GenericItem) item).setCommandDescriptionService(null); + } + } + @Reference(cardinality = ReferenceCardinality.OPTIONAL, policy = ReferencePolicy.DYNAMIC) protected void setManagedProvider(ManagedItemProvider provider) { super.setManagedProvider(provider); diff --git a/bundles/org.openhab.core/src/main/java/org/eclipse/smarthome/core/internal/service/CommandDescriptionServiceImpl.java b/bundles/org.openhab.core/src/main/java/org/eclipse/smarthome/core/internal/service/CommandDescriptionServiceImpl.java new file mode 100644 index 00000000000..a484839bea1 --- /dev/null +++ b/bundles/org.openhab.core/src/main/java/org/eclipse/smarthome/core/internal/service/CommandDescriptionServiceImpl.java @@ -0,0 +1,67 @@ +/** + * Copyright (c) 2014,2019 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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.eclipse.smarthome.core.internal.service; + +import java.util.List; +import java.util.Locale; +import java.util.concurrent.CopyOnWriteArrayList; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.smarthome.core.service.CommandDescriptionService; +import org.eclipse.smarthome.core.types.CommandDescription; +import org.eclipse.smarthome.core.types.CommandDescriptionProvider; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ReferenceCardinality; +import org.osgi.service.component.annotations.ReferencePolicy; + +/** + * The {@link CommandDescriptionService} combines all available {@link CommandDescriptionProvider} implementations to + * build a resulting {@link CommandDescription}. + * + * @author Henning Treu - Initial contribution + * + */ +@NonNullByDefault +@Component +public class CommandDescriptionServiceImpl implements CommandDescriptionService { + + private final List commandDescriptionProviders = new CopyOnWriteArrayList<>(); + + @Override + public @Nullable CommandDescription getCommandDescription(String itemName, @Nullable Locale locale) { + /* + * As of now there is only the ChannelCommandDescriptionProvider, so there is no merge logic as for + * {@link StateDescriptionFragment}s. Just take the first CommandDescription which was provided. + */ + for (CommandDescriptionProvider cdp : commandDescriptionProviders) { + CommandDescription commandDescription = cdp.getCommandDescription(itemName, locale); + if (commandDescription != null) { + return commandDescription; + } + } + + return null; + } + + @Reference(cardinality = ReferenceCardinality.MULTIPLE, policy = ReferencePolicy.DYNAMIC) + protected void addCommandDescriptionProvider(CommandDescriptionProvider commandDescriptionProvider) { + commandDescriptionProviders.add(commandDescriptionProvider); + } + + protected void removeCommandDescriptionProvider(CommandDescriptionProvider commandDescriptionProvider) { + commandDescriptionProviders.remove(commandDescriptionProvider); + } + +} diff --git a/bundles/org.openhab.core/src/main/java/org/eclipse/smarthome/core/internal/types/CommandDescriptionImpl.java b/bundles/org.openhab.core/src/main/java/org/eclipse/smarthome/core/internal/types/CommandDescriptionImpl.java new file mode 100644 index 00000000000..6ea9f61b3a8 --- /dev/null +++ b/bundles/org.openhab.core/src/main/java/org/eclipse/smarthome/core/internal/types/CommandDescriptionImpl.java @@ -0,0 +1,51 @@ +/** + * Copyright (c) 2014,2019 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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.eclipse.smarthome.core.internal.types; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.smarthome.core.types.CommandDescription; +import org.eclipse.smarthome.core.types.CommandOption; + +/** + * The {@link CommandDescriptionImpl} groups state command properties. + * + * @author Henning Treu - initial contribution + * + */ +@NonNullByDefault +public class CommandDescriptionImpl implements CommandDescription { + + private final List commandOptions; + + public CommandDescriptionImpl() { + commandOptions = new ArrayList<>(); + } + + /** + * Adds a {@link CommandOption} to this {@link CommandDescriptionImpl}. + * + * @param commandOption a commandOption to be added to this {@link CommandDescriptionImpl}. + */ + public void addCommandOption(CommandOption commandOption) { + commandOptions.add(commandOption); + } + + @Override + public List getCommandOptions() { + return Collections.unmodifiableList(commandOptions); + } +} diff --git a/bundles/org.openhab.core/src/main/java/org/eclipse/smarthome/core/items/GenericItem.java b/bundles/org.openhab.core/src/main/java/org/eclipse/smarthome/core/items/GenericItem.java index 749879223b4..5bc8a7c241f 100644 --- a/bundles/org.openhab.core/src/main/java/org/eclipse/smarthome/core/items/GenericItem.java +++ b/bundles/org.openhab.core/src/main/java/org/eclipse/smarthome/core/items/GenericItem.java @@ -31,8 +31,12 @@ import org.eclipse.smarthome.core.events.EventPublisher; import org.eclipse.smarthome.core.i18n.UnitProvider; import org.eclipse.smarthome.core.items.events.ItemEventFactory; +import org.eclipse.smarthome.core.service.CommandDescriptionService; import org.eclipse.smarthome.core.service.StateDescriptionService; import org.eclipse.smarthome.core.types.Command; +import org.eclipse.smarthome.core.types.CommandDescription; +import org.eclipse.smarthome.core.types.CommandDescriptionBuilder; +import org.eclipse.smarthome.core.types.CommandOption; import org.eclipse.smarthome.core.types.RefreshType; import org.eclipse.smarthome.core.types.State; import org.eclipse.smarthome.core.types.StateDescription; @@ -78,6 +82,8 @@ public abstract class GenericItem implements ActiveItem { private @Nullable StateDescriptionService stateDescriptionService; + private @Nullable CommandDescriptionService commandDescriptionService; + protected @Nullable UnitProvider unitProvider; protected @Nullable ItemStateConverter itemStateConverter; @@ -170,6 +176,7 @@ public void dispose() { this.listeners.clear(); this.eventPublisher = null; this.stateDescriptionService = null; + this.commandDescriptionService = null; this.unitProvider = null; this.itemStateConverter = null; } @@ -182,6 +189,10 @@ public void setStateDescriptionService(@Nullable StateDescriptionService stateDe this.stateDescriptionService = stateDescriptionService; } + public void setCommandDescriptionService(@Nullable CommandDescriptionService commandDescriptionService) { + this.commandDescriptionService = commandDescriptionService; + } + public void setUnitProvider(@Nullable UnitProvider unitProvider) { this.unitProvider = unitProvider; } @@ -401,6 +412,23 @@ public void setCategory(@Nullable String category) { return null; } + @Override + public @Nullable CommandDescription getCommandDescription(@Nullable Locale locale) { + if (commandDescriptionService != null) { + CommandDescription commandDescription = commandDescriptionService.getCommandDescription(this.name, locale); + if (commandDescription != null) { + return commandDescription; + } + } + + StateDescription stateDescription = getStateDescription(locale); + if (stateDescription != null && !stateDescription.getOptions().isEmpty()) { + return stateOptions2CommandOptions(stateDescription); + } + + return null; + } + /** * Tests if state is within acceptedDataTypes list or a subclass of one of them * @@ -418,4 +446,12 @@ protected void logSetTypeError(State state) { state.getClass().getSimpleName(), getName(), getClass().getSimpleName()); } + private @Nullable CommandDescription stateOptions2CommandOptions(StateDescription stateDescription) { + CommandDescriptionBuilder builder = CommandDescriptionBuilder.create(); + stateDescription.getOptions() + .forEach(so -> builder.withCommandOption(new CommandOption(so.getValue(), so.getLabel()))); + + return builder.build(); + } + } diff --git a/bundles/org.openhab.core/src/main/java/org/eclipse/smarthome/core/items/Item.java b/bundles/org.openhab.core/src/main/java/org/eclipse/smarthome/core/items/Item.java index 4c24b262608..85a80fa290f 100644 --- a/bundles/org.openhab.core/src/main/java/org/eclipse/smarthome/core/items/Item.java +++ b/bundles/org.openhab.core/src/main/java/org/eclipse/smarthome/core/items/Item.java @@ -22,8 +22,11 @@ import org.eclipse.smarthome.core.library.types.OnOffType; import org.eclipse.smarthome.core.library.types.PercentType; import org.eclipse.smarthome.core.types.Command; +import org.eclipse.smarthome.core.types.CommandDescription; +import org.eclipse.smarthome.core.types.CommandOption; import org.eclipse.smarthome.core.types.State; import org.eclipse.smarthome.core.types.StateDescription; +import org.eclipse.smarthome.core.types.StateOption; import org.eclipse.smarthome.core.types.UnDefType; /** @@ -152,4 +155,25 @@ public interface Item extends Identifiable { */ public @Nullable StateDescription getStateDescription(@Nullable Locale locale); + /** + * Returns the {@link CommandDescription} for this item. In case no dedicated {@link CommandDescription} is + * provided the {@link StateOption}s from the {@link StateDescription} will be served as valid + * {@link CommandOption}s. + * + * @return the {@link CommandDescription} for the default locale (can be null). + */ + public default @Nullable CommandDescription getCommandDescription() { + return getCommandDescription(null); + } + + /** + * Returns the {@link CommandDescription} for the given locale. In case no dedicated {@link CommandDescription} is + * provided the {@link StateOption}s from the {@link StateDescription} will be served as valid + * {@link CommandOption}s. + * + * @param locale locale (can be null) + * @return command description (can be null) + */ + public @Nullable CommandDescription getCommandDescription(@Nullable Locale locale); + } diff --git a/bundles/org.openhab.core/src/main/java/org/eclipse/smarthome/core/service/CommandDescriptionService.java b/bundles/org.openhab.core/src/main/java/org/eclipse/smarthome/core/service/CommandDescriptionService.java new file mode 100644 index 00000000000..8135cf4b3d7 --- /dev/null +++ b/bundles/org.openhab.core/src/main/java/org/eclipse/smarthome/core/service/CommandDescriptionService.java @@ -0,0 +1,39 @@ +/** + * Copyright (c) 2014,2019 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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.eclipse.smarthome.core.service; + +import java.util.Locale; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; +import org.eclipse.smarthome.core.types.CommandDescription; + +/** + * An implementation of this service provides locale specific {@link CommandDescription}s for the given item. + * + * @author Henning Treu - Initial contribution + * + */ +@NonNullByDefault +public interface CommandDescriptionService { + + /** + * Returns the locale specific {@link CommandDescription} for the given item name. + * + * @param itemName the name of the item + * @param locale the locale for translated command labels + * @return the locale specific {@link CommandDescription} for the given item name + */ + @Nullable + CommandDescription getCommandDescription(String itemName, @Nullable Locale locale); +} diff --git a/bundles/org.openhab.core/src/main/java/org/eclipse/smarthome/core/types/CommandDescription.java b/bundles/org.openhab.core/src/main/java/org/eclipse/smarthome/core/types/CommandDescription.java new file mode 100644 index 00000000000..77b36aaeddd --- /dev/null +++ b/bundles/org.openhab.core/src/main/java/org/eclipse/smarthome/core/types/CommandDescription.java @@ -0,0 +1,34 @@ +/** + * Copyright (c) 2014,2019 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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.eclipse.smarthome.core.types; + +import java.util.List; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * The {@link CommandDescription} groups state command properties. + * + * @author Henning Treu - initial contribution + * + */ +@NonNullByDefault +public interface CommandDescription { + + /** + * An unmodifiable list of {@link CommandOption}s. + * + * @return An unmodifiable list of {@link CommandOption}s + */ + List getCommandOptions(); +} diff --git a/bundles/org.openhab.core/src/main/java/org/eclipse/smarthome/core/types/CommandDescriptionBuilder.java b/bundles/org.openhab.core/src/main/java/org/eclipse/smarthome/core/types/CommandDescriptionBuilder.java new file mode 100644 index 00000000000..ea8a73eb350 --- /dev/null +++ b/bundles/org.openhab.core/src/main/java/org/eclipse/smarthome/core/types/CommandDescriptionBuilder.java @@ -0,0 +1,49 @@ +/** + * Copyright (c) 2014,2019 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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.eclipse.smarthome.core.types; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.smarthome.core.internal.types.CommandDescriptionImpl; + +/** + * Used to build instances of {@link CommandDescription}. + * + * @author Henning Treu - Initial contribution + * + */ +public class CommandDescriptionBuilder { + + private final List commandOptions = new ArrayList<>(); + + private CommandDescriptionBuilder() { + // prevent public instantiation + } + + public static CommandDescriptionBuilder create() { + return new CommandDescriptionBuilder(); + } + + public void withCommandOption(CommandOption commandOption) { + this.commandOptions.add(commandOption); + } + + public CommandDescription build() { + CommandDescriptionImpl commandDescription = new CommandDescriptionImpl(); + commandOptions.forEach(co -> commandDescription.addCommandOption(co)); + + return commandDescription; + } + +} diff --git a/bundles/org.openhab.core/src/main/java/org/eclipse/smarthome/core/types/CommandDescriptionProvider.java b/bundles/org.openhab.core/src/main/java/org/eclipse/smarthome/core/types/CommandDescriptionProvider.java new file mode 100644 index 00000000000..4e5a1544f54 --- /dev/null +++ b/bundles/org.openhab.core/src/main/java/org/eclipse/smarthome/core/types/CommandDescriptionProvider.java @@ -0,0 +1,38 @@ +/** + * Copyright (c) 2014,2019 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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.eclipse.smarthome.core.types; + +import java.util.Locale; + +import org.eclipse.jdt.annotation.NonNullByDefault; +import org.eclipse.jdt.annotation.Nullable; + +/** + * Implementations provide an item specific, localized {@link CommandDescription}. + * + * @author Henning Treu - Initial contribution + * + */ +@NonNullByDefault +public interface CommandDescriptionProvider { + + /** + * Returns the item specific, localized {@link CommandDescription}. + * + * @param itemName the name of the item + * @param locale the locale + * @return the item specific, localized {@link CommandDescription} + */ + @Nullable + CommandDescription getCommandDescription(String itemName, @Nullable Locale locale); +} diff --git a/bundles/org.openhab.core/src/main/java/org/eclipse/smarthome/core/types/CommandOption.java b/bundles/org.openhab.core/src/main/java/org/eclipse/smarthome/core/types/CommandOption.java new file mode 100644 index 00000000000..fba863ebb65 --- /dev/null +++ b/bundles/org.openhab.core/src/main/java/org/eclipse/smarthome/core/types/CommandOption.java @@ -0,0 +1,52 @@ +/** + * Copyright (c) 2014,2019 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * 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.eclipse.smarthome.core.types; + +import java.nio.channels.Channel; + +import org.eclipse.jdt.annotation.NonNullByDefault; + +/** + * Represents a command option for "write only" command {@link Channel}s. CommandOptions will be rendered as + * push-buttons in the UI and will not represent a state. + * + * @author Henning Treu - Initial contribution + * + */ +@NonNullByDefault +public class CommandOption { + + /** + * The command which will be send to the Channel + */ + private final String command; + + /** + * The name of the command which will be displayed in the UI. + */ + private final String label; + + public CommandOption(String command, String label) { + this.command = command; + this.label = label; + } + + public String getCommand() { + return command; + } + + public String getLabel() { + return label; + } + +} diff --git a/itests/org.openhab.core.model.thing.tests/src/main/java/org/eclipse/smarthome/model/thing/test/hue/TestHueChannelTypeProvider.java b/itests/org.openhab.core.model.thing.tests/src/main/java/org/eclipse/smarthome/model/thing/test/hue/TestHueChannelTypeProvider.java index 03578236fb1..1e73ee9e39d 100644 --- a/itests/org.openhab.core.model.thing.tests/src/main/java/org/eclipse/smarthome/model/thing/test/hue/TestHueChannelTypeProvider.java +++ b/itests/org.openhab.core.model.thing.tests/src/main/java/org/eclipse/smarthome/model/thing/test/hue/TestHueChannelTypeProvider.java @@ -24,6 +24,7 @@ import org.eclipse.smarthome.core.thing.type.ChannelGroupTypeProvider; import org.eclipse.smarthome.core.thing.type.ChannelGroupTypeUID; import org.eclipse.smarthome.core.thing.type.ChannelType; +import org.eclipse.smarthome.core.thing.type.ChannelTypeBuilder; import org.eclipse.smarthome.core.thing.type.ChannelTypeProvider; import org.eclipse.smarthome.core.thing.type.ChannelTypeUID; import org.osgi.service.component.annotations.Component; @@ -50,16 +51,24 @@ public class TestHueChannelTypeProvider implements ChannelTypeProvider, ChannelG public TestHueChannelTypeProvider() { try { - ChannelType ctColor = new ChannelType(COLOR_CHANNEL_TYPE_UID, false, "Color", "colorLabel", "description", - null, null, null, new URI("hue", "LCT001:color", null)); - ChannelType ctColorTemperature = new ChannelType(COLOR_TEMP_CHANNEL_TYPE_UID, false, "Dimmer", - "colorTemperatureLabel", "description", null, null, null, - new URI("hue", "LCT001:color_temperature", null)); - ChannelType ctColorX = new ChannelType(COLORX_CHANNEL_TYPE_UID, false, "Color", "colorLabel", "description", - null, null, null, new URI("Xhue", "XLCT001:Xcolor", null)); - ChannelType ctColorTemperatureX = new ChannelType(COLORX_TEMP_CHANNEL_TYPE_UID, false, "Dimmer", - "colorTemperatureLabel", "description", null, null, null, - new URI("Xhue", "XLCT001:Xcolor_temperature", null)); + ChannelType ctColor = ChannelTypeBuilder.state(COLOR_CHANNEL_TYPE_UID, "colorLabel", "Color") + .withDescription("description").withConfigDescriptionURI(new URI("hue", "LCT001:color", null)) + .build(); + + ChannelType ctColorTemperature = ChannelTypeBuilder + .state(COLOR_TEMP_CHANNEL_TYPE_UID, "colorTemperatureLabel", "Dimmer") + .withDescription("description") + .withConfigDescriptionURI(new URI("hue", "LCT001:color_temperature", null)).build(); + + ChannelType ctColorX = ChannelTypeBuilder.state(COLORX_CHANNEL_TYPE_UID, "colorLabel", "Color") + .withDescription("description").withConfigDescriptionURI(new URI("Xhue", "XLCT001:Xcolor", null)) + .build(); + + ChannelType ctColorTemperatureX = ChannelTypeBuilder + .state(COLORX_TEMP_CHANNEL_TYPE_UID, "colorTemperatureLabel", "Dimmer") + .withDescription("description") + .withConfigDescriptionURI(new URI("Xhue", "XLCT001:Xcolor_temperature", null)).build(); + channelTypes = Arrays.asList(ctColor, ctColorTemperature, ctColorX, ctColorTemperatureX); ChannelGroupType groupX = ChannelGroupTypeBuilder.instance(GROUP_CHANNEL_GROUP_TYPE_UID, "Channel Group") diff --git a/itests/org.openhab.core.tests/src/main/java/org/eclipse/smarthome/core/items/GenericItemTest.java b/itests/org.openhab.core.tests/src/main/java/org/eclipse/smarthome/core/items/GenericItemTest.java index 9ca9e30f0f6..13f3c7fd4c8 100644 --- a/itests/org.openhab.core.tests/src/main/java/org/eclipse/smarthome/core/items/GenericItemTest.java +++ b/itests/org.openhab.core.tests/src/main/java/org/eclipse/smarthome/core/items/GenericItemTest.java @@ -12,11 +12,16 @@ */ package org.eclipse.smarthome.core.items; +import static org.hamcrest.collection.IsCollectionWithSize.hasSize; import static org.junit.Assert.*; +import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import java.util.Arrays; +import java.util.List; +import java.util.Locale; +import org.eclipse.jdt.annotation.NonNull; import org.eclipse.smarthome.core.events.EventPublisher; import org.eclipse.smarthome.core.i18n.UnitProvider; import org.eclipse.smarthome.core.items.events.ItemStateChangedEvent; @@ -24,7 +29,13 @@ import org.eclipse.smarthome.core.library.types.PercentType; import org.eclipse.smarthome.core.library.types.RawType; import org.eclipse.smarthome.core.library.types.StringType; +import org.eclipse.smarthome.core.service.CommandDescriptionService; +import org.eclipse.smarthome.core.service.StateDescriptionService; +import org.eclipse.smarthome.core.types.CommandDescription; +import org.eclipse.smarthome.core.types.CommandOption; import org.eclipse.smarthome.core.types.State; +import org.eclipse.smarthome.core.types.StateDescriptionFragmentBuilder; +import org.eclipse.smarthome.core.types.StateOption; import org.junit.Test; import org.mockito.ArgumentCaptor; @@ -34,6 +45,7 @@ * @author Christoph Knauf - Initial contribution, event tests * @author Simon Kaufmann - migrated from Groovy to Java */ +@SuppressWarnings("null") public class GenericItemTest { @Test @@ -130,6 +142,56 @@ public void testDispose() { assertEquals(0, item.listeners.size()); } + @Test + public void testCommandDescription() { + TestItem item = new TestItem("test"); + + CommandDescriptionService commandDescriptionService = mock(CommandDescriptionService.class); + when(commandDescriptionService.getCommandDescription("test", null)).thenReturn(new CommandDescription() { + + @Override + public @NonNull List<@NonNull CommandOption> getCommandOptions() { + return Arrays.asList(new CommandOption("ALERT", "Alert"), new CommandOption("REBOOT", "Reboot")); + } + }); + item.setCommandDescriptionService(commandDescriptionService); + + assertThat(item.getCommandDescription().getCommandOptions(), hasSize(2)); + } + + @Test + public void testCommandDescriptionWithLocale() { + TestItem item = new TestItem("test"); + + CommandDescriptionService commandDescriptionService = mock(CommandDescriptionService.class); + when(commandDescriptionService.getCommandDescription(eq("test"), any(Locale.class))) + .thenReturn(new CommandDescription() { + + @Override + public @NonNull List<@NonNull CommandOption> getCommandOptions() { + return Arrays.asList(new CommandOption("C1", "Command 1"), new CommandOption("C2", "Command 2"), + new CommandOption("C3", "Command 3")); + } + }); + item.setCommandDescriptionService(commandDescriptionService); + + assertThat(item.getCommandDescription(Locale.getDefault()).getCommandOptions(), hasSize(3)); + } + + @Test + public void commandDescriptionShouldHaveStateOptionsAsCommands() { + TestItem item = new TestItem("test"); + + StateDescriptionService stateDescriptionService = mock(StateDescriptionService.class); + List<@NonNull StateOption> stateOptions = Arrays.asList(new StateOption("STATE1", "State 1"), + new StateOption("STATE2", "State 2")); + when(stateDescriptionService.getStateDescription("test", null)).thenReturn( + StateDescriptionFragmentBuilder.create().withOptions(stateOptions).build().toStateDescription()); + item.setStateDescriptionService(stateDescriptionService); + + assertThat(item.getCommandDescription().getCommandOptions(), hasSize(2)); + } + /** * Fooling the null-analysis tooling * diff --git a/itests/org.openhab.core.tests/src/main/java/org/eclipse/smarthome/core/items/ItemRegistryImplTest.java b/itests/org.openhab.core.tests/src/main/java/org/eclipse/smarthome/core/items/ItemRegistryImplTest.java index f88703298f0..a21cfb2347c 100644 --- a/itests/org.openhab.core.tests/src/main/java/org/eclipse/smarthome/core/items/ItemRegistryImplTest.java +++ b/itests/org.openhab.core.tests/src/main/java/org/eclipse/smarthome/core/items/ItemRegistryImplTest.java @@ -36,6 +36,7 @@ import org.eclipse.smarthome.core.library.items.NumberItem; import org.eclipse.smarthome.core.library.items.StringItem; import org.eclipse.smarthome.core.library.items.SwitchItem; +import org.eclipse.smarthome.core.service.CommandDescriptionService; import org.eclipse.smarthome.core.service.StateDescriptionService; import org.eclipse.smarthome.test.java.JavaTest; import org.eclipse.smarthome.test.storage.VolatileStorageService; @@ -382,4 +383,28 @@ public void assertOldItemIsBeingDisposedOnUpdate() { assertEquals(0, item.listeners.size()); } + @Test + public void assertCommandDescriptionServiceGetsInjected() { + GenericItem item = spy(new SwitchItem("Item1")); + itemProvider.add(item); + + verify(item).setCommandDescriptionService(null); + + ((ItemRegistryImpl) itemRegistry).setCommandDescriptionService(mock(CommandDescriptionService.class)); + verify(item).setCommandDescriptionService(any(CommandDescriptionService.class)); + } + + @Test + public void assertCommandDescriptionServiceGetsRemoved() { + CommandDescriptionService commandDescriptionService = mock(CommandDescriptionService.class); + ((ItemRegistryImpl) itemRegistry).setCommandDescriptionService(commandDescriptionService); + + GenericItem item = spy(new SwitchItem("Item1")); + itemProvider.add(item); + verify(item).setCommandDescriptionService(any(CommandDescriptionService.class)); + + ((ItemRegistryImpl) itemRegistry).unsetCommandDescriptionService(commandDescriptionService); + verify(item).setCommandDescriptionService(null); + } + } diff --git a/itests/org.openhab.core.thing.tests/src/main/java/org/eclipse/smarthome/core/thing/internal/ChannelStateDescriptionProviderOSGiTest.java b/itests/org.openhab.core.thing.tests/src/main/java/org/eclipse/smarthome/core/thing/internal/ChannelStateDescriptionProviderOSGiTest.java index 83341bbec86..9aeadfa7c78 100644 --- a/itests/org.openhab.core.thing.tests/src/main/java/org/eclipse/smarthome/core/thing/internal/ChannelStateDescriptionProviderOSGiTest.java +++ b/itests/org.openhab.core.thing.tests/src/main/java/org/eclipse/smarthome/core/thing/internal/ChannelStateDescriptionProviderOSGiTest.java @@ -109,13 +109,13 @@ public void setup() { final ChannelType channelType2 = new ChannelType(new ChannelTypeUID("hue:num"), false, "Number", " ", "", null, null, state2, null); final ChannelType channelType3 = new ChannelType(new ChannelTypeUID("hue:info"), true, "String", " ", "", null, - null, null, null); + null, (StateDescription) null, null); final ChannelType channelType4 = new ChannelType(new ChannelTypeUID("hue:color"), false, "Color", "Color", "", - "ColorLight", null, null, null); + "ColorLight", null, (StateDescription) null, null); final ChannelType channelType5 = new ChannelType(new ChannelTypeUID("hue:brightness"), false, "Dimmer", - "Brightness", "", "DimmableLight", null, null, null); + "Brightness", "", "DimmableLight", null, (StateDescription) null, null); final ChannelType channelType6 = new ChannelType(new ChannelTypeUID("hue:switch"), false, "Switch", "Switch", - "", "Light", null, null, null); + "", "Light", null, (StateDescription) null, null); final ChannelType channelType7 = new ChannelType(new ChannelTypeUID("hue:num-dynamic"), false, "Number", " ", "", "Light", null, state, null); diff --git a/itests/org.openhab.core.thing.xml.tests/src/main/java/org/eclipse/smarthome/core/thing/xml/test/ChannelTypesI18nTest.java b/itests/org.openhab.core.thing.xml.tests/src/main/java/org/eclipse/smarthome/core/thing/xml/test/ChannelTypesI18nTest.java index caddd5449f0..1b42e364ff4 100644 --- a/itests/org.openhab.core.thing.xml.tests/src/main/java/org/eclipse/smarthome/core/thing/xml/test/ChannelTypesI18nTest.java +++ b/itests/org.openhab.core.thing.xml.tests/src/main/java/org/eclipse/smarthome/core/thing/xml/test/ChannelTypesI18nTest.java @@ -76,6 +76,10 @@ public void channelTypesShouldTranslateCorrectly() throws Exception { assertThat(channelType1, is(not(nullValue()))); assertThat(channelType1.getLabel(), is(equalTo("Channel Label"))); assertThat(channelType1.getDescription(), is(equalTo("Channel Description"))); + assertThat(channelType1.getCommandDescription().getCommandOptions().get(0).getLabel(), + is(equalTo("Short Alarm"))); + assertThat(channelType1.getCommandDescription().getCommandOptions().get(1).getLabel(), + is(equalTo("Long Alarm"))); Collection channelGroupTypes = channelGroupTypeProvider.getChannelGroupTypes(null); ChannelGroupType channelGroupType = channelGroupTypes.stream() diff --git a/itests/org.openhab.core.thing.xml.tests/src/main/resources/test-bundle-pool/ChannelTypesI18nTest.bundle/src/main/resources/ESH-INF/i18n/somebinding.properties b/itests/org.openhab.core.thing.xml.tests/src/main/resources/test-bundle-pool/ChannelTypesI18nTest.bundle/src/main/resources/ESH-INF/i18n/somebinding.properties index 42cc5722863..fef0e10c0bf 100644 --- a/itests/org.openhab.core.thing.xml.tests/src/main/resources/test-bundle-pool/ChannelTypesI18nTest.bundle/src/main/resources/ESH-INF/i18n/somebinding.properties +++ b/itests/org.openhab.core.thing.xml.tests/src/main/resources/test-bundle-pool/ChannelTypesI18nTest.bundle/src/main/resources/ESH-INF/i18n/somebinding.properties @@ -4,3 +4,6 @@ channelGroupLabel = Channel Group Label channelGroupDescription = Channel Group Description channelInplaceLabel = Channel Inplace Label channelInplaceDescription = Channel Inplace Description + +channel-type.somebinding.channel-with-i18n.command.option.ALARM = Short Alarm +channel-type.somebinding.channel-with-i18n.command.option.LONG_ALARM = Long Alarm diff --git a/itests/org.openhab.core.thing.xml.tests/src/main/resources/test-bundle-pool/ChannelTypesI18nTest.bundle/src/main/resources/ESH-INF/thing/thing-types.xml b/itests/org.openhab.core.thing.xml.tests/src/main/resources/test-bundle-pool/ChannelTypesI18nTest.bundle/src/main/resources/ESH-INF/thing/thing-types.xml index 339c6db9c0c..b69d8846cbd 100644 --- a/itests/org.openhab.core.thing.xml.tests/src/main/resources/test-bundle-pool/ChannelTypesI18nTest.bundle/src/main/resources/ESH-INF/thing/thing-types.xml +++ b/itests/org.openhab.core.thing.xml.tests/src/main/resources/test-bundle-pool/ChannelTypesI18nTest.bundle/src/main/resources/ESH-INF/thing/thing-types.xml @@ -1,6 +1,8 @@ - + @@ -22,6 +24,12 @@ Number @text/channelDescription + + + + +