Skip to content
Permalink
Browse files

[mqtt] Rollershutter STOP command, outgoing transform, chained incomi…

…ng transformations (#6695)

* MQTT: Rollershutter STOP command, outgoing transform

The Rollershutter channel stores its value as percentage.
UP/DOWN/STOP commands received via MQTT do not change that state.

Instead of trying to handle those commands, they are now
posted to the framework.

* Allow outgoing transformations.
* Allow chained incoming transformations.
* Add more examples to the readme.

* PercentageChannel: Return value between min and max. Homie: Remove  handling completely. Readme: Add retain and isCommand and some more mqtt1 details. Rollershutter: Fix

Signed-off-by: David Graeff <david.graeff@web.de>
  • Loading branch information
davidgraeff authored and kaikreuzer committed Jan 18, 2019
1 parent 53be3da commit d739f6ae10809f60bff810dbc74f505e120dec52
Showing with 652 additions and 319 deletions.
  1. +2 −3 ...neric.test/src/test/java/org/eclipse/smarthome/binding/mqtt/generic/HomieImplementationTests.java
  2. +2 −1 .../src/test/java/org/eclipse/smarthome/binding/mqtt/generic/internal/generic/ChannelStateTests.java
  3. +2 −2 .../org/eclipse/smarthome/binding/mqtt/generic/internal/generic/ChannelStateTransformationTests.java
  4. +1 −34 ...test/java/org/eclipse/smarthome/binding/mqtt/generic/internal/handler/HomieThingHandlerTests.java
  5. +25 −5 ...ric.test/src/test/java/org/eclipse/smarthome/binding/mqtt/generic/internal/values/ValueTests.java
  6. +47 −19 ...nsions/binding/org.eclipse.smarthome.binding.mqtt.generic/ESH-INF/config/color-channel-config.xml
  7. +87 −0 ...sions/binding/org.eclipse.smarthome.binding.mqtt.generic/ESH-INF/config/dimmer-channel-config.xml
  8. +47 −19 ...sions/binding/org.eclipse.smarthome.binding.mqtt.generic/ESH-INF/config/number-channel-config.xml
  9. +47 −19 ...inding/org.eclipse.smarthome.binding.mqtt.generic/ESH-INF/config/rollershutter-channel-config.xml
  10. +47 −19 ...sions/binding/org.eclipse.smarthome.binding.mqtt.generic/ESH-INF/config/string-channel-config.xml
  11. +37 −11 ...sions/binding/org.eclipse.smarthome.binding.mqtt.generic/ESH-INF/config/switch-channel-config.xml
  12. +1 −1 extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/ESH-INF/thing/channels.xml
  13. +146 −16 extensions/binding/org.eclipse.smarthome.binding.mqtt.generic/README.md
  14. +2 −2 .../org/eclipse/smarthome/binding/mqtt/generic/internal/convention/homeassistant/ComponentLight.java
  15. +3 −45 ...src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/convention/homie300/Device.java
  16. +0 −14 .../java/org/eclipse/smarthome/binding/mqtt/generic/internal/convention/homie300/DeviceCallback.java
  17. +0 −31 ...rg/eclipse/smarthome/binding/mqtt/generic/internal/convention/homie300/DeviceStatsAttributes.java
  18. +5 −8 ...rc/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/discovery/Homie300Discovery.java
  19. +1 −0 ...eric/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/generic/ChannelConfig.java
  20. +28 −5 ...neric/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/generic/ChannelState.java
  21. +1 −1 .../java/org/eclipse/smarthome/binding/mqtt/generic/internal/generic/ChannelStateUpdateListener.java
  22. +3 −1 ...ain/java/org/eclipse/smarthome/binding/mqtt/generic/internal/generic/MqttChannelTypeProvider.java
  23. +0 −2 ...va/org/eclipse/smarthome/binding/mqtt/generic/internal/generic/TransformationServiceProvider.java
  24. +1 −1 ...in/java/org/eclipse/smarthome/binding/mqtt/generic/internal/handler/AbstractMQTTThingHandler.java
  25. +8 −1 ...rc/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/handler/GenericThingHandler.java
  26. +1 −35 .../src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/handler/HomieThingHandler.java
  27. +33 −3 ...generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/values/NumberValue.java
  28. +32 −2 ...ric/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/values/PercentageValue.java
  29. +24 −15 .../src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/values/RollershutterValue.java
  30. +18 −3 ....mqtt.generic/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/values/Value.java
  31. +1 −1 ...eneric/src/main/java/org/eclipse/smarthome/binding/mqtt/generic/internal/values/ValueFactory.java
@@ -35,7 +35,6 @@
import org.eclipse.smarthome.binding.mqtt.generic.internal.convention.homie300.DeviceAttributes;
import org.eclipse.smarthome.binding.mqtt.generic.internal.convention.homie300.DeviceAttributes.ReadyState;
import org.eclipse.smarthome.binding.mqtt.generic.internal.convention.homie300.DeviceCallback;
import org.eclipse.smarthome.binding.mqtt.generic.internal.convention.homie300.DeviceStatsAttributes;
import org.eclipse.smarthome.binding.mqtt.generic.internal.convention.homie300.Node;
import org.eclipse.smarthome.binding.mqtt.generic.internal.convention.homie300.NodeAttributes;
import org.eclipse.smarthome.binding.mqtt.generic.internal.convention.homie300.Property;
@@ -248,8 +247,8 @@ public void parseHomieTree() throws InterruptedException, ExecutionException, Ti
// Create a Homie Device object. Because spied Nodes are required for call verification,
// the full Device constructor need to be used and a ChildMap object need to be created manually.
ChildMap<Node> nodeMap = new ChildMap<>();
Device device = spy(new Device(ThingChannelConstants.testHomieThing, callback, new DeviceAttributes(),
new DeviceStatsAttributes(), nodeMap, Device.createDeviceStatisticsListener(handler)));
Device device = spy(
new Device(ThingChannelConstants.testHomieThing, callback, new DeviceAttributes(), nodeMap));

// Intercept creating a node in initialize()->start() and inject a spy'ed node.
doAnswer(this::createSpyNode).when(device).createNode(any());
@@ -192,7 +192,8 @@ public void receiveDecimalFractionalTest() throws InterruptedException, Executio

@Test
public void receivePercentageTest() throws InterruptedException, ExecutionException, TimeoutException {
PercentageValue value = new PercentageValue(new BigDecimal(-100), new BigDecimal(100), new BigDecimal(10));
PercentageValue value = new PercentageValue(new BigDecimal(-100), new BigDecimal(100), new BigDecimal(10), null,
null);
ChannelState c = spy(new ChannelState(config, channelUID, value, channelStateUpdateListener));
c.start(connection, mock(ScheduledExecutorService.class), 100);

@@ -106,7 +106,7 @@ public void initialize() throws MqttException {

thingHandler.initialize();
ChannelState channelConfig = thingHandler.getChannelState(textChannelUID);
assertThat(channelConfig.transformations.get(0).pattern, is(jsonPathPattern));
assertThat(channelConfig.transformationsIn.get(0).pattern, is(jsonPathPattern));
}

@SuppressWarnings("null")
@@ -118,7 +118,7 @@ public void processMessageWithJSONPath() throws Exception {
ChannelState channelConfig = thingHandler.getChannelState(textChannelUID);
channelConfig.setChannelStateUpdateListener(thingHandler);

ChannelStateTransformation transformation = channelConfig.transformations.get(0);
ChannelStateTransformation transformation = channelConfig.transformationsIn.get(0);

byte payload[] = jsonPathJSON.getBytes();
assertThat(transformation.pattern, is(jsonPathPattern));
@@ -34,7 +34,6 @@
import org.eclipse.smarthome.binding.mqtt.generic.internal.convention.homie300.Device;
import org.eclipse.smarthome.binding.mqtt.generic.internal.convention.homie300.DeviceAttributes;
import org.eclipse.smarthome.binding.mqtt.generic.internal.convention.homie300.DeviceAttributes.ReadyState;
import org.eclipse.smarthome.binding.mqtt.generic.internal.convention.homie300.DeviceStatsAttributes;
import org.eclipse.smarthome.binding.mqtt.generic.internal.convention.homie300.Node;
import org.eclipse.smarthome.binding.mqtt.generic.internal.convention.homie300.NodeAttributes;
import org.eclipse.smarthome.binding.mqtt.generic.internal.convention.homie300.Property;
@@ -132,8 +131,7 @@ public void setUp() {
thingHandler = spy(handler);
thingHandler.setCallback(callback);
final Device device = new Device(thing.getUID(), thingHandler, spy(new DeviceAttributes()),
spy(new DeviceStatsAttributes()), new ChildMap<>(),
Device.createDeviceStatisticsListener(thingHandler));
spy(new ChildMap<>()));
thingHandler.setInternalObjects(spy(device),
spy(new DelayedBatchProcessing<Object>(500, thingHandler, scheduler)));

@@ -366,35 +364,4 @@ public void propertiesChanged() throws InterruptedException, ExecutionException
assertThat(properties.get(MqttBindingConstants.HOMIE_PROPERTY_VERSION), is("3.0"));
assertThat(properties.size(), is(1));
}

@Test
public void heartBeatInterval()
throws InterruptedException, ExecutionException, NoSuchFieldException, SecurityException {
thingHandler.device.initialize("homie", "device", new ArrayList<Channel>());
thingHandler.connection = connection;

// Inject spy'ed subscriber object
doAnswer(this::createSubscriberAnswer).when(thingHandler.device.stats).createSubscriber(any(), any(), any(),
anyBoolean());
doAnswer(this::createSubscriberAnswer).when(thingHandler.device.attributes).createSubscriber(any(), any(),
any(), anyBoolean());

thingHandler.device.attributes.state = ReadyState.ready;
thingHandler.device.attributes.name = "device";
thingHandler.device.attributes.homie = "3.0";
thingHandler.device.attributes.nodes = new String[] {};

thingHandler.initialize();

assertThat(thingHandler.device.isInitialized(), is(true));

verify(thingHandler.device).attributesReceived(any(), any(), anyInt());
verify(thingHandler.device.stats).subscribeAndReceive(any(), any(), anyString(), any(), anyInt());

// Emulate a received value for the "interval" topic
thingHandler.device.stats.fieldChanged(DeviceStatsAttributes.class.getDeclaredField("interval"), 60);

verify(thingHandler).heartbeatIntervalChanged(anyInt());
verify(callback).thingUpdated(any());
}
}
@@ -83,7 +83,7 @@ public void illegalNumberCommand() {

@Test(expected = IllegalArgumentException.class)
public void illegalPercentCommand() {
PercentageValue v = new PercentageValue(null, null, null);
PercentageValue v = new PercentageValue(null, null, null, null, null);
v.update(OnOffType.OFF);
}

@@ -95,7 +95,7 @@ public void illegalOnOffCommand() {

@Test(expected = IllegalArgumentException.class)
public void illegalPercentUpdate() {
PercentageValue v = new PercentageValue(null, null, null);
PercentageValue v = new PercentageValue(null, null, null, null, null);
v.update(new DecimalType(101.0));
}

@@ -180,16 +180,35 @@ public void rollershutterUpdate() {

@Test
public void percentCalc() {
PercentageValue v = new PercentageValue(new BigDecimal(10.0), new BigDecimal(110.0), new BigDecimal(1.0));
PercentageValue v = new PercentageValue(new BigDecimal(10.0), new BigDecimal(110.0), new BigDecimal(1.0), null,
null);
v.update(new DecimalType(110.0));
assertThat((PercentType) v.getChannelState(), is(new PercentType(100)));
assertThat(v.getMQTTpublishValue(), is("110"));
v.update(new DecimalType(10.0));
assertThat((PercentType) v.getChannelState(), is(new PercentType(0)));
assertThat(v.getMQTTpublishValue(), is("10"));

v.update(OnOffType.ON);
assertThat((PercentType) v.getChannelState(), is(new PercentType(100)));
v.update(OnOffType.OFF);
assertThat((PercentType) v.getChannelState(), is(new PercentType(0)));
}

@Test
public void percentCustomOnOff() {
PercentageValue v = new PercentageValue(new BigDecimal(0.0), new BigDecimal(100.0), new BigDecimal(1.0), "on",
"off");
v.update(new StringType("on"));
assertThat((PercentType) v.getChannelState(), is(new PercentType(100)));
v.update(new StringType("off"));
assertThat((PercentType) v.getChannelState(), is(new PercentType(0)));
}

@Test
public void decimalCalc() {
PercentageValue v = new PercentageValue(new BigDecimal(0.1), new BigDecimal(1.0), new BigDecimal(0.1));
PercentageValue v = new PercentageValue(new BigDecimal(0.1), new BigDecimal(1.0), new BigDecimal(0.1), null,
null);
v.update(new DecimalType(1.0));
assertThat((PercentType) v.getChannelState(), is(new PercentType(100)));
v.update(new DecimalType(0.1));
@@ -200,7 +219,8 @@ public void decimalCalc() {

@Test(expected = IllegalArgumentException.class)
public void percentCalcInvalid() {
PercentageValue v = new PercentageValue(new BigDecimal(10.0), new BigDecimal(110.0), new BigDecimal(1.0));
PercentageValue v = new PercentageValue(new BigDecimal(10.0), new BigDecimal(110.0), new BigDecimal(1.0), null,
null);
v.update(new DecimalType(9.0));
}
}
@@ -5,25 +5,53 @@
xsi:schemaLocation="http://eclipse.org/smarthome/schemas/config-description/v1.0.0 http://eclipse.org/smarthome/schemas/config-description-1.0.0.xsd">

<config-description uri="thing-type:mqtt:color_channel">
<parameter name="stateTopic" type="text">
<label>MQTT state topic</label>
<description>An MQTT topic that this thing will subscribe to, to receive the state. This can be left empty, the channel will be state-less command-only channel.</description>
</parameter>
<parameter name="commandTopic" type="text">
<label>MQTT command topic</label>
<description>An MQTT topic that this thing will send a command to. If not set, this will be a read-only switch.</description>
</parameter>
<parameter name="transformationPattern" type="text">
<label>Incoming value transformation</label>
<description>Applies a transformation to an incoming MQTT topic value. A transformation example for a received JSON would be "JSONPATH:$.device.status.temperature" for a json {device: {status: { temperature: 23.2 }}}. Any supported transformation service can be used.</description>
<advanced>true</advanced>
</parameter>
<parameter name="formatBeforePublish" type="text">
<label>Outgoing value format</label>
<description>Format a value before it is published to the MQTT broker. The default is to just pass the channel/item state. If you want to apply a prefix, say "MYCOLOR,", you would use "MYCOLOR,%s". If you want to adjust the precision of a number to for example 4 digits, you would use "%.4f".</description>
<advanced>true</advanced>
<default>%s</default>
</parameter>
<parameter-group name="transformations">
<label>Transform values</label>
<description>These configuration parameters allow you to alter a value before it is published to MQTT or before a received value is assigned to an item.</description>
<advanced>true</advanced>
</parameter-group>

<parameter name="stateTopic" type="text">
<label>MQTT state topic</label>
<description>An MQTT topic that this thing will subscribe to, to receive the state. This can be left empty, the channel will be state-less command-only channel.</description>
</parameter>
<parameter name="commandTopic" type="text">
<label>MQTT command topic</label>
<description>An MQTT topic that this thing will send a command to. If not set, this will be a read-only switch.</description>
</parameter>
<parameter name="transformationPattern" type="text" groupName="transformations">
<label>Incoming value transformations</label>
<description><![CDATA[
Applies transformations to an incoming MQTT topic value.
A transformation example for a received JSON would be "JSONPATH:$.device.status.temperature" for
a json {device: {status: { temperature: 23.2 }}}.
You can chain transformations by separating them with the intersection character ∩.
]]></description>
<advanced>true</advanced>
</parameter>
<parameter name="transformationPatternOut" type="text" groupName="transformations">
<label>Outgoing value transformation</label>
<description><![CDATA[
Applies a transformation before publishing a MQTT topic value.
Transformations are specialised in extracting a value, but some transformations like
the MAP one could be useful.
]]></description>
<advanced>true</advanced>
</parameter>
<parameter name="formatBeforePublish" type="text" groupName="transformations">
<label>Outgoing value format</label>
<description><![CDATA[
Format a value before it is published to the MQTT broker.
The default is to just pass the channel/item state.
If you want to apply a prefix, say "MYCOLOR,", you would use "MYCOLOR,%s".
If you want to adjust the precision of a number to for example 4 digits, you would use "%.4f".
]]></description>
<advanced>true</advanced>
<default>%s</default>
</parameter>
<parameter name="retained" type="boolean">
<label>Retained</label>
<description>The value will be published to the command topic as retained message. A retained value stays on the broker and can even be seen by MQTT clients that are subscribing at a later point in time.</description>
@@ -0,0 +1,87 @@
<?xml version="1.0" encoding="UTF-8"?>
<config-description:config-descriptions
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:config-description="http://eclipse.org/smarthome/schemas/config-description/v1.0.0"
xsi:schemaLocation="http://eclipse.org/smarthome/schemas/config-description/v1.0.0 http://eclipse.org/smarthome/schemas/config-description-1.0.0.xsd">

<config-description uri="thing-type:mqtt:dimmer_channel">
<parameter-group name="transformations">
<label>Transform values</label>
<description>These configuration parameters allow you to alter a value before it is published to MQTT or before a received value is assigned to an item.</description>
<advanced>true</advanced>
</parameter-group>

<parameter name="stateTopic" type="text">
<label>MQTT state topic</label>
<description>An MQTT topic that this thing will subscribe to, to receive the state. This can be left empty, the channel will be state-less command-only channel.</description>
</parameter>
<parameter name="commandTopic" type="text">
<label>MQTT command topic</label>
<description>An MQTT topic that this thing will send a command to. If not set, this will be a read-only switch.</description>
</parameter>
<parameter name="transformationPattern" type="text" groupName="transformations">
<label>Incoming value transformations</label>
<description><![CDATA[
Applies transformations to an incoming MQTT topic value.
A transformation example for a received JSON would be "JSONPATH:$.device.status.temperature" for
a json {device: {status: { temperature: 23.2 }}}.
You can chain transformations by separating them with the intersection character ∩.
]]></description>
<advanced>true</advanced>
</parameter>
<parameter name="transformationPatternOut" type="text" groupName="transformations">
<label>Outgoing value transformation</label>
<description><![CDATA[
Applies a transformation before publishing a MQTT topic value.
Transformations are specialised in extracting a value, but some transformations like
the MAP one could be useful.
]]></description>
<advanced>true</advanced>
</parameter>
<parameter name="formatBeforePublish" type="text" groupName="transformations">
<label>Outgoing value format</label>
<description><![CDATA[
Format a value before it is published to the MQTT broker.
The default is to just pass the channel/item state.
If you want to apply a prefix, say "MYCOLOR,", you would use "MYCOLOR,%s".
If you want to adjust the precision of a number to for example 4 digits, you would use "%.4f".
]]></description>
<advanced>true</advanced>
<default>%s</default>
</parameter>
<parameter name="retained" type="boolean">
<label>Retained</label>
<description>The value will be published to the command topic as retained message. A retained value stays on the broker and can even be seen by MQTT clients that are subscribing at a later point in time.</description>
<default>false</default>
<advanced>true</advanced>
</parameter>

<parameter name="min" type="decimal">
<label>Absolute minimum</label>
<description>This configuration represents the minimum of the allowed range. For a percentage channel that equals zero percent.</description>
</parameter>
<parameter name="max" type="decimal">
<label>Absolute maximum</label>
<description>This configuration represents the maximum of the allowed range. For a percentage channel that equals one-hundred percent.</description>
</parameter>
<parameter name="step" type="decimal">
<label>Delta value</label>
<description>A number/dimmer channel can receive Increase/Decrease commands and computes the target number by adding or subtracting this delta value.</description>
<default>1.0</default>
<advanced>true</advanced>
</parameter>
<parameter name="on" type="text">
<label>Custom On/Open value</label>
<description>A number (like 1, 10) or a string (like "enabled") that is additionally recognised as on/open state. You can use this parameter for a second keyword, next to ON (OPEN respectively on a Contact).</description>
<default>1</default>
</parameter>
<parameter name="off" type="text">
<label>Custom Off/Closed value</label>
<description>A number (like 0, -10) or a string (like "disabled") that is additionally recognised as off/closed state. You can use this parameter for a second keyword, next to OFF (CLOSED respectively on a Contact).</description>
<default>0</default>
</parameter>
</config-description>
</config-description:config-descriptions>

0 comments on commit d739f6a

Please sign in to comment.
You can’t perform that action at this time.