Skip to content

Commit

Permalink
Extend ChannelType by command options
Browse files Browse the repository at this point in the history
This addresses eclipse-archived/smarthome#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 <[email protected]>
  • Loading branch information
htreu committed Feb 6, 2019
1 parent a6a1d59 commit 79c60f1
Show file tree
Hide file tree
Showing 34 changed files with 949 additions and 30 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand All @@ -31,11 +32,12 @@ public class EnrichedItemDTO extends ItemDTO {
public String state;
public String transformedState;
public StateDescription stateDescription;
public CommandDescription commandDescription;
public Map<String, Object> 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;
Expand All @@ -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;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,14 @@
<item-type>String</item-type>
<label>Alert</label>
<description>The alert channel allows a temporary change to the bulb’s state.</description>
<state>
<command>
<options>
<option value="NONE">None</option>
<option value="SELECT">Alert</option>
<option value="LSELECT">Long Alert</option>
</options>
</state>
</command>

<autoUpdatePolicy>recommend</autoUpdatePolicy>
</channel-type>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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<String, String> attributes, NodeIterator nodeIterator) throws ConversionException {
Expand All @@ -157,6 +173,7 @@ protected ChannelTypeXmlResult unmarshalType(HierarchicalStreamReader reader, Un
Set<String> tags = readTags(nodeIterator);

StateDescription stateDescription = readStateDescription(nodeIterator);
CommandDescription commandDescription = readCommandDescription(nodeIterator);
EventDescription eventDescription = readEventDescription(nodeIterator);

AutoUpdatePolicy autoUpdatePolicy = readAutoUpdatePolicy(nodeIterator);
Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
* <p>
* This converter converts {@code command} XML tags.
*
* @author Henning Treu - Initial Contribution
*/
public class CommandDescriptionConverter extends GenericUnmarshaller<CommandDescription> {

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;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -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);
Expand All @@ -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);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
<xs:element name="category" type="xs:string" minOccurs="0"/>
<xs:element name="tags" type="thing-description:tags" minOccurs="0"/>
<xs:element name="state" type="thing-description:state" minOccurs="0"/>
<xs:element name="command" type="thing-description:command" minOccurs="0"/>
<xs:element name="event" type="thing-description:event" minOccurs="0"/>
<xs:element name="autoUpdatePolicy" type="thing-description:auto-update-policy" minOccurs="0"/>
<xs:choice minOccurs="0">
Expand Down Expand Up @@ -170,6 +171,12 @@
</xs:sequence>
</xs:complexType>

<xs:complexType name="command">
<xs:sequence>
<xs:element name="options" type="thing-description:options" minOccurs="0"/>
</xs:sequence>
</xs:complexType>

<xs:complexType name="option">
<xs:simpleContent>
<xs:extension base="xs:string">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand All @@ -38,14 +40,15 @@ public class ChannelTypeDTO {
public Set<String> tags;
public String UID;
public boolean advanced;
public CommandDescription commandDescription;

public ChannelTypeDTO() {
}

public ChannelTypeDTO(String UID, String label, String description, String category, String itemType,
ChannelKind kind, List<ConfigDescriptionParameterDTO> parameters,
List<ConfigDescriptionParameterGroupDTO> parameterGroups, StateDescription stateDescription,
Set<String> tags, boolean advanced) {
Set<String> tags, boolean advanced, CommandDescription commandDescription) {
this.UID = UID;
this.label = label;
this.description = description;
Expand All @@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand All @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"));
Expand Down
Loading

0 comments on commit 79c60f1

Please sign in to comment.