Skip to content
This repository has been archived by the owner on May 7, 2020. It is now read-only.

Commit

Permalink
Extend ChannelType by command options (#5131)
Browse files Browse the repository at this point in the history
* WIP: Extend ChannelType description by command options

This is WIP and open for discussion.

This addresses #5099 by adding command options as an alternative to a state description.
The implementation is naive and straigt forward.
Command options will be rendered as push buttons by UIs and send the corresponding command value as a command to the channel.
With this proposal, the state of the channel will not be represented in the UI, so ThingHandelers may not even update the state.

Signed-off-by: Henning Treu <[email protected]>
  • Loading branch information
htreu authored and kaikreuzer committed Jan 31, 2019
1 parent 9612308 commit 7c6c6bb
Show file tree
Hide file tree
Showing 51 changed files with 1,064 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,30 @@
*/
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;
import org.eclipse.smarthome.core.library.types.OnOffType;
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;

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

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
<stringAttribute key="pde.version" value="3.3"/>
<stringAttribute key="product" value="org.eclipse.equinox.p2.director.app.product"/>
<booleanAttribute key="run_in_ui_thread" value="false"/>
<stringAttribute key="selected_target_plugins" value="ch.qos.logback.classic@default:default,ch.qos.logback.core@default:default,ch.qos.logback.slf4j@default:false,com.eclipsesource.jaxrs.jersey-min@default:default,com.google.gson@default:default,com.google.guava@default:default,javax.activation@default:default,javax.measure.unit-api@default:default,javax.servlet@default:default,javax.transaction@default:false,javax.xml@default:default,net.bytebuddy.byte-buddy-agent@default:default,net.bytebuddy.byte-buddy@default:default,org.apache.ant@default:default,org.apache.commons.collections@default:default,org.apache.commons.io@default:default,org.apache.commons.lang@default:default,org.apache.commons.logging@default:default,org.apache.felix.gogo.command@default:default,org.apache.felix.gogo.runtime@default:default,org.apache.felix.scr@1:true,org.codehaus.groovy@default:default,org.eclipse.core.contenttype@default:default,org.eclipse.core.jobs@default:default,org.eclipse.core.runtime@default:true,org.eclipse.emf.common@default:default,org.eclipse.emf.ecore.xmi@default:default,org.eclipse.emf.ecore@default:default,org.eclipse.equinox.app@default:default,org.eclipse.equinox.common@2:true,org.eclipse.equinox.ds@1:true,org.eclipse.equinox.preferences@default:default,org.eclipse.equinox.region@default:false,org.eclipse.equinox.registry@default:default,org.eclipse.equinox.transforms.hook@default:false,org.eclipse.equinox.util@default:default,org.eclipse.equinox.weaving.hook@default:false,org.eclipse.jetty.http@default:default,org.eclipse.jetty.io@default:default,org.eclipse.jetty.osgi.alpn.fragment@default:false,org.eclipse.jetty.security@default:default,org.eclipse.jetty.server@default:default,org.eclipse.jetty.servlet@default:default,org.eclipse.jetty.util@default:default,org.eclipse.osgi.services@default:default,org.eclipse.osgi.util@default:default,org.eclipse.osgi@-1:true,org.hamcrest.core@default:default,org.hamcrest.integration@default:default,org.hamcrest.library@default:default,org.hamcrest.text@default:default,org.hamcrest@default:default,org.junit@default:default,org.mockito.mockito-core@default:default,org.objenesis@default:default,org.slf4j.api@default:default,org.slf4j.log4j@default:default,tec.uom.lib.uom-lib-common@default:default,tec.uom.se@default:default"/>
<stringAttribute key="selected_target_plugins" value="ch.qos.logback.classic@default:default,ch.qos.logback.core@default:default,ch.qos.logback.slf4j@default:false,com.eclipsesource.jaxrs.jersey-min@default:default,com.google.gson*2.2.4.v201311231704@default:default,com.google.gson*2.7.0.v20170129-0911@default:default,com.google.guava@default:default,javax.activation@default:default,javax.measure.unit-api@default:default,javax.servlet*3.1.0.v20140303-1611@default:default,javax.servlet*3.1.0.v201410161800@default:default,javax.transaction@default:false,javax.xml@default:default,net.bytebuddy.byte-buddy-agent@default:default,net.bytebuddy.byte-buddy@default:default,org.apache.ant@default:default,org.apache.commons.collections@default:default,org.apache.commons.io@default:default,org.apache.commons.lang@default:default,org.apache.commons.logging@default:default,org.apache.felix.gogo.command@default:default,org.apache.felix.gogo.runtime@default:default,org.apache.felix.scr@1:true,org.codehaus.groovy@default:default,org.eclipse.core.contenttype@default:default,org.eclipse.core.jobs@default:default,org.eclipse.core.runtime@default:true,org.eclipse.emf.common@default:default,org.eclipse.emf.ecore.xmi@default:default,org.eclipse.emf.ecore@default:default,org.eclipse.equinox.app@default:default,org.eclipse.equinox.common@2:true,org.eclipse.equinox.ds@1:true,org.eclipse.equinox.preferences@default:default,org.eclipse.equinox.region@default:false,org.eclipse.equinox.registry@default:default,org.eclipse.equinox.transforms.hook@default:false,org.eclipse.equinox.util@default:default,org.eclipse.equinox.weaving.hook@default:false,org.eclipse.jetty.http*9.3.15.v20161220@default:default,org.eclipse.jetty.http*9.4.5.v20170502@default:default,org.eclipse.jetty.io*9.3.15.v20161220@default:default,org.eclipse.jetty.io*9.4.5.v20170502@default:default,org.eclipse.jetty.osgi.alpn.fragment@default:false,org.eclipse.jetty.security*9.3.15.v20161220@default:default,org.eclipse.jetty.security*9.4.5.v20170502@default:default,org.eclipse.jetty.server*9.3.15.v20161220@default:default,org.eclipse.jetty.server*9.4.5.v20170502@default:default,org.eclipse.jetty.servlet*9.3.15.v20161220@default:default,org.eclipse.jetty.servlet*9.4.5.v20170502@default:default,org.eclipse.jetty.util*9.3.15.v20161220@default:default,org.eclipse.jetty.util*9.4.5.v20170502@default:default,org.eclipse.osgi.services@default:default,org.eclipse.osgi.util@default:default,org.eclipse.osgi@-1:true,org.hamcrest.core@default:default,org.hamcrest.integration@default:default,org.hamcrest.library@default:default,org.hamcrest.text@default:default,org.hamcrest@default:default,org.junit@default:default,org.mockito.mockito-core@default:default,org.objenesis@default:default,org.slf4j.api@default:default,org.slf4j.log4j@default:default,tec.uom.lib.uom-lib-common@default:default,tec.uom.se@default:default"/>
<stringAttribute key="selected_workspace_plugins" value="org.eclipse.smarthome.config.core@default:true,org.eclipse.smarthome.config.xml@default:true,org.eclipse.smarthome.core.thing.xml.test@default:false,org.eclipse.smarthome.core.thing.xml@default:true,org.eclipse.smarthome.core.thing@default:true,org.eclipse.smarthome.core@default:true,org.eclipse.smarthome.io.console@default:default,org.eclipse.smarthome.test@default:default"/>
<booleanAttribute key="show_selected_only" value="false"/>
<booleanAttribute key="tracing" value="false"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<ChannelGroupType> channelGroupTypes = channelGroupTypeProvider.getChannelGroupTypes(null);
ChannelGroupType channelGroupType = channelGroupTypes.stream()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<thing:thing-descriptions bindingId="somebinding" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="http://eclipse.org/smarthome/schemas/thing-description/v1.0.0" xsi:schemaLocation="http://eclipse.org/smarthome/schemas/thing-description/v1.0.0 org.eclipse.smarthome.thing-description.xsd">
<thing:thing-descriptions bindingId="somebinding"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:thing="http://eclipse.org/smarthome/schemas/thing-description/v1.0.0"
xsi:schemaLocation="http://eclipse.org/smarthome/schemas/thing-description/v1.0.0 org.eclipse.smarthome.thing-description.xsd">

<thing-type id="something">

Expand All @@ -22,6 +24,12 @@
<item-type>Number</item-type>
<label>@text/channelLabel</label>
<description>@text/channelDescription</description>
<command>
<options>
<option value="ALARM">Alarm</option>
<option value="LONG_ALARM" />
</options>
</command>
</channel-type>

<channel-group-type id="channelgroup-with-i18n">
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
Loading

0 comments on commit 7c6c6bb

Please sign in to comment.