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

Commit

Permalink
PercentageChannel: Return value between min and max. Homie: Remove ha…
Browse files Browse the repository at this point in the history
…ndling 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
David Graeff committed Jan 11, 2019
1 parent 7263489 commit 0c153b3
Show file tree
Hide file tree
Showing 12 changed files with 171 additions and 171 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)));

Expand Down Expand Up @@ -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());
}
}
Original file line number Diff line number Diff line change
@@ -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>
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
<channel-type id="dimmer">
<item-type>Dimmer</item-type>
<label>Percentage value</label>
<config-description-ref uri="thing-type:mqtt:number_channel"></config-description-ref>
<config-description-ref uri="thing-type:mqtt:dimmer_channel"></config-description-ref>
</channel-type>

<channel-type id="switch">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,8 @@ All things require a configured broker.
* __transformationPatternOut__: An optional transformation pattern like [JSONPath](http://goessner.net/articles/JsonPath/index.html#e2) that is applied before publishing a value to MQTT.
* __commandTopic__: The MQTT topic that commands are send to. This can be empty, the thing channel will be read-only then. Transformations are not applied for sending data.
* __formatBeforePublish__: 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".
* __postCommand__: If the received MQTT value should not only update the state of linked items, but command them, enable this option. You usually need this enabled if your item is also linked to another channel, say a KNX actor, and you want a received MQTT payload to command that KNX actor.
* __retained__: 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.

### Channel Type "string"

Expand Down Expand Up @@ -226,6 +228,13 @@ Here are a few examples:
- For an output of *23.05.1995* use "%1$**td**.%1$**tm**.%1$**tY**".
- For an output of *23:15* use "%1$**tH**:%1$**tM**".

## Troubleshooting

* If you get the error "No MQTT client": Please update your installation.
* If you use the Mosquitto broker: Please be aware that there is a relatively low setting
for retained messages. At some point messages will just not being delivered
anymore: Change the setting

## Examples

Have a look at the following textual examples.
Expand Down Expand Up @@ -311,10 +320,14 @@ end
The conversion is straight forward, but need to be done for each item.
You do not need to convert everything in one go. MQTT1 and MQTT2 can coexist.

> For mqtt1 make sure you have enabled the Legacy 1.x repository and installed "mqtt1".
### 1 Command / 1 State topic

Assume you have this item:

```xtend
Switch ExampleItem "Heatpump Power" { mqtt=">[mosquitto:heatpump/set:command:*:DEFAULT)],<[mosquitto:heatpump:state:JSONPATH($.power)]" }
Switch ExampleItem "Heatpump Power" { mqtt=">[mosquitto:heatpump/set:command:*:DEFAULT)],<[mosquitto:heatpump/state:JSONPATH($.power)]" }
```

This converts to an entry in your *.things file with a **Broker Thing** and a **Generic MQTT Thing** that uses the bridge:
Expand All @@ -336,3 +349,33 @@ Your items change to:
```xtend
Switch ExampleItem "Heatpump Power" { channel="mqtt:myUnsecureBroker:topic:mything:heatpumpChannel" }
```


### 1 Command / 2 State topics

If you receive updates from two different topics, you need to create multiple channels now, 1 for each MQTT receive topic.

```xtend
Switch ExampleItem "Heatpump Power" { mqtt=">[mosquitto:heatpump/set:command:*:DEFAULT)],<[mosquitto:heatpump/state1:state:*:DEFAULT",<[mosquitto:heatpump/state2:state:*:DEFAULT" }
```

This converts to:

```xtend
Bridge mqtt:broker:myUnsecureBroker [ host="192.168.0.42", secure=false ]
{
Thing mqtt:topic:mything {
Channels:
Type switch : heatpumpChannel "Heatpump Power" [ stateTopic="heatpump/state1", commandTopic="heatpump/set" ]
Type switch : heatpumpChannel2 "Heatpump Power" [ stateTopic="heatpump/state2" ]
}
}
```

Link both channels to one item. That item will publish to "heatpump/set" on a change and
receive values from "heatpump/state1" and "heatpump/state2".

```xtend
Switch ExampleItem "Heatpump Power" { channel="mqtt:myUnsecureBroker:topic:mything:heatpumpChannel",
channel="mqtt:myUnsecureBroker:topic:mything:heatpumpChannel2" }
```
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
import org.eclipse.jdt.annotation.Nullable;
import org.eclipse.smarthome.binding.mqtt.generic.internal.handler.HomieThingHandler;
import org.eclipse.smarthome.binding.mqtt.generic.internal.mapping.AbstractMqttAttributeClass;
import org.eclipse.smarthome.binding.mqtt.generic.internal.mapping.AbstractMqttAttributeClass.AttributeChanged;
import org.eclipse.smarthome.binding.mqtt.generic.internal.tools.ChildMap;
import org.eclipse.smarthome.core.thing.Channel;
import org.eclipse.smarthome.core.thing.ChannelUID;
Expand Down Expand Up @@ -50,7 +49,6 @@ public class Device implements AbstractMqttAttributeClass.AttributeChanged {
private final Logger logger = LoggerFactory.getLogger(Device.class);
// The device attributes, statistics and nodes of this device
public final DeviceAttributes attributes;
public final DeviceStatsAttributes stats;
public final ChildMap<Node> nodes;

// The corresponding ThingUID and callback of this device object
Expand All @@ -61,24 +59,19 @@ public class Device implements AbstractMqttAttributeClass.AttributeChanged {
private String topic = "";
public String deviceID = "";
private boolean initialized = false;
private final AttributeChanged deviceStatisticsListener;

/**
* Creates a Homie Device structure. It consists of device attributes, device statistics and nodes.
*
* @param thingUID The thing UID
* @param callback A callback, used to notify about new/removed nodes/properties and more.
* @param attributes The device attributes object
* @param stats The device statistics object
*/
public Device(ThingUID thingUID, DeviceCallback callback, DeviceAttributes attributes,
DeviceStatsAttributes stats) {
public Device(ThingUID thingUID, DeviceCallback callback, DeviceAttributes attributes) {
this.thingUID = thingUID;
this.callback = callback;
this.attributes = attributes;
this.stats = stats;
this.nodes = new ChildMap<>();
this.deviceStatisticsListener = createDeviceStatisticsListener(callback);
}

/**
Expand All @@ -87,40 +80,13 @@ public Device(ThingUID thingUID, DeviceCallback callback, DeviceAttributes attri
* @param thingUID The thing UID
* @param callback A callback, used to notify about new/removed nodes/properties and more.
* @param attributes The device attributes object
* @param stats The device statistics object
* @param nodes The nodes map
* @param deviceStatisticsListener The AttributeChanged listener for the device statistics. Create the default one
* with {@link #createDeviceStatisticsListener(DeviceCallback)}.
*/
public Device(ThingUID thingUID, DeviceCallback callback, DeviceAttributes attributes, DeviceStatsAttributes stats,
ChildMap<Node> nodes, AttributeChanged deviceStatisticsListener) {
public Device(ThingUID thingUID, DeviceCallback callback, DeviceAttributes attributes, ChildMap<Node> nodes) {
this.thingUID = thingUID;
this.callback = callback;
this.attributes = attributes;
this.stats = stats;
this.nodes = nodes;
this.deviceStatisticsListener = deviceStatisticsListener;
}

/**
* Create the default listener for the device statistic attributes object.
*
* <p>
* To be used for the
* {@link #Device(ThingUID, DeviceCallback, DeviceAttributes, DeviceStatsAttributes, ChildMap, AttributeChanged)}
* constructor.
* </p>
*
* @param callback A device callback
* @return The listener
*/
public static AttributeChanged createDeviceStatisticsListener(DeviceCallback callback) {
return (String name, Object value, MqttBrokerConnection connection, ScheduledExecutorService scheduler,
boolean allMandatoryFieldsReceived) -> {
if ("interval".equals(name)) {
callback.heartbeatIntervalChanged((int) value);
}
};
}

/**
Expand Down Expand Up @@ -154,14 +120,6 @@ public static AttributeChanged createDeviceStatisticsListener(DeviceCallback cal
public CompletableFuture<@Nullable Void> attributesReceived(MqttBrokerConnection connection,
ScheduledExecutorService scheduler, int timeout) {
callback.readyStateChanged(attributes.state);
// Subscribe to statistics attributes
stats.subscribeAndReceive(connection, scheduler, topic + "/$stats", deviceStatisticsListener, timeout)
.exceptionally(e -> {
logger.warn("Did not receive all required device statistics attributes!");
// Default heartbeat interval assumed
callback.heartbeatIntervalChanged(stats.interval);
return null;
});
return applyNodes(connection, scheduler, timeout);
}

Expand Down Expand Up @@ -207,7 +165,7 @@ public static AttributeChanged createDeviceStatisticsListener(DeviceCallback cal
* Unsubscribe from everything.
*/
public CompletableFuture<@Nullable Void> stop() {
return attributes.unsubscribe().thenCompose(b -> stats.unsubscribe()).thenCompose(
return attributes.unsubscribe().thenCompose(
b -> CompletableFuture.allOf(nodes.stream().map(n -> n.stop()).toArray(CompletableFuture[]::new)));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,27 +24,13 @@
*/
@NonNullByDefault
public interface DeviceCallback extends ChannelStateUpdateListener {
/**
* Called whenever the heartbeat interval changes.
*
* @param intervalInSec Interval in seconds.
*/
void heartbeatIntervalChanged(int intervalInSec);

/**
* Called whenever the device state changed
*
* @param state The new state
*/
void readyStateChanged(ReadyState state);

/**
* Called whenever the statistics properties changed
*
* @param stats The new statistics
*/
void statisticAttributesChanged(DeviceStatsAttributes stats);

/**
* Called, whenever a Homie node was existing before, but is not anymore.
*
Expand Down
Loading

0 comments on commit 0c153b3

Please sign in to comment.