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

MQTT Generic Binding #5101

Closed
wants to merge 13 commits into from
Closed

Conversation

sjsf
Copy link
Contributor

@sjsf sjsf commented Feb 16, 2018

This is a rebased version of #3876 with commits on top which

  • incorporate (my...) review feedback (see Mqtt things #3876 (review))
  • update the license headers
  • adaptation to mockito 2
  • adding the binding to the docu structure
  • Eclipse & Karaf features

For details & discussion about the binding please see #3876.

Also-by: David Gräff david.graeff@web.de
Also-by: Henning Treu henning.treu@telekom.de
Signed-off-by: Simon Kaufmann simon.kfm@googlemail.com

David Gräff and others added 3 commits February 16, 2018 11:06
Bind Mqtt topics to things. Broker connections are listed as brigdes. A generic thing
can be added. The thing can host one or multiple channels, each linked to a mqtt state / command
topic and an own optional transformation. A channel is one of the following types:

* TextChannel
* NumberChannel
* PercentageChannel
* OnOffChannel

A channel can be configured with a transformation pattern
for an incoming message from a Mqtt state topic. This allows to extract a value from
JSON/XML/etc, as it seems to get popular on some Mqtt topics to encode multiple values into json.

The handlers and all value classes are backed by tests.

Signed-off-by: David Gräff <david.graeff@web.de>
Signed-off-by: Simon Kaufmann <simon.kfm@googlemail.com>
Signed-off-by: Simon Kaufmann <simon.kfm@googlemail.com>
@sjsf sjsf changed the title Mqtt things rebased MQTT Generic Binding Feb 16, 2018
@sjsf sjsf mentioned this pull request Feb 16, 2018
Signed-off-by: Simon Kaufmann <simon.kfm@googlemail.com>
Copy link
Contributor

@htreu htreu left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did a quick review, no checkout but reading the code online. See inline comments.

private Thing thing;

@Mock
MqttBrokerConnectionHandler bridgeHandler;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

private

MqttBrokerConnectionHandler bridgeHandler;

@Mock
MqttBrokerConnection connection;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

private

@Mock
MqttBrokerConnection connection;

MqttThingHandler subject;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

private & should be named thingHandler for better readability.

// JSonpathservice implementation. Unfortunately, we can't use the real class, because
// it is not exported. An OSGI test is to heavy and unnecessary to just have those few lines
// of code for a jsonPath transformation.
TransformationService jsonPathService = (jsonPathExpression, source) -> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does this implementation exist at all? It could be a simple mock. Otherwise only JsonPath#read would silently be tested here, too.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No idea - would think so too.

@Test
public void processMessageWithJSONPath() {
subject.initialize();
ChannelConfig c = subject.channelDataByChannelUID.get(textChannelUID);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would be nicer to read if c would be channelConfig.

* @author David Graeff - Initial contribution
*/
public class OnOffValue implements AbstractMqttThingValue {
OnOffType boolValue;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

private

public class OnOffValue implements AbstractMqttThingValue {
OnOffType boolValue;
boolean isInversedOnOff = false;
String onValue;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

private

OnOffType boolValue;
boolean isInversedOnOff = false;
String onValue;
String offValue;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

private

* @author David Graeff - Initial contribution
*/
public class TextValue implements AbstractMqttThingValue {
StringType strValue;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

private

@Component(service = DiscoveryService.class, immediate = true, name = "MqttServiceDiscoveryService")
public class MqttServiceDiscoveryService extends AbstractDiscoveryService implements MqttBrokersObserver {
private final Logger logger = LoggerFactory.getLogger(MqttServiceDiscoveryService.class);
MqttService mqttService;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

private

* Review comments

Signed-off-by: Henning Treu <henning.treu@telekom.de>
super.modified(configProperties);
}

@Reference(cardinality = ReferenceCardinality.MANDATORY)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need to set the cardinality explicit to mandatory as this is the implicit one.

@maggu2810
Copy link
Contributor

@SJKA Is this code ready to be merged from your side? Then I will try to find some time for a detailed review.

Signed-off-by: Simon Kaufmann <simon.kfm@googlemail.com>
@sjsf
Copy link
Contributor Author

sjsf commented Mar 6, 2018

Sure, that would be great. As you probably have noticed I'm not the original author either, so I'd happily accept PRs to this branch as well, to keep the overall effort as low as possible for all of us.

@davidgraeff
Copy link
Contributor

I have finished all other Mqtt PRs of mine (Mqtt embedded broker, MqttService, Mqtt broker connections) and like to continue on this one. Where do we stand currently? I guess support for newer PaperUI features like #4146 and #4142 need this binding to be adapted?

@htreu
Copy link
Contributor

htreu commented Mar 20, 2018

@davidgraeff
Copy link
Contributor

@sjsf
Copy link
Contributor Author

sjsf commented Mar 28, 2018

...and with #5301 being rejected I think this should be ready for @maggu2810's review.

Afterwards it'll need to be run through the Eclipse IP review (a.k.a. CQ) as it exceeds the 1000 LoC.

@davidgraeff
Copy link
Contributor

You need to set the bindingid to mqtt though. As I understood Kai, this binding, a homie mqtt binding later on, and the mqtt broker connection binding basically need the same bindingid.

@sjsf
Copy link
Contributor Author

sjsf commented Mar 28, 2018

Argh, sure!

Signed-off-by: Simon Kaufmann <simon.kfm@googlemail.com>
@davidgraeff
Copy link
Contributor

Are dots not allowed in binding directory names? The "mqtt.generic" name wasn't too bad.

@sjsf
Copy link
Contributor Author

sjsf commented Mar 28, 2018

No, dots aren't the problem.

I just had a second look at how it was done in the bluetooth case - and realized that in the end one bundle has to be the "main" MQTT one, providing e.g. the binding.xml file and the main frame for the documentation. And therefore my assumption was that this is the best candidate. Isn't it?

@davidgraeff
Copy link
Contributor

It depends on the bundle of #3839, but as Kai mentioned somewhere, those two bundles can be grouped together in one feature. So yes it is a good candidate.

* eclipse-archived#4173 changes some APIs, adapt to those.
* Remove the broker connection thing, we are going to use the one defined in eclipse-archived#3839
* Remove all discovery stuff

Signed-off-by: David Gräff <david.graeff@web.de>
@sjsf
Copy link
Contributor Author

sjsf commented Mar 28, 2018

Yes, they will be both included in the same karaf feature.

As #3839 actually contains even more "basic" features of the binding, it might in fact be the even better candidate for the "o.e.sh.binding.mqtt" namespace. Would you rename the folders of #3839? Then I will add the .generic here to the bundle path and the packages again. Okay?

Let's move/fuse binding.xml, README and the like later on. It's already chaotic enough...

@davidgraeff
Copy link
Contributor

Haha oh gosh. I just reimported the updated mqtt.generic without generic into eclipse. But yeah will do. There are two more things in this PR:

  • I broke the tests by removing the discovery stuff and the bridge.
  • The thing-types.xml need a change: The parent bridge types (there are two!) are : "broker" and "systemBroker".

@sjsf
Copy link
Contributor Author

sjsf commented Mar 28, 2018

I just reimported the updated mqtt.generic without generic into eclipse. But yeah will do.

Sorry & thanks - you were just too quick for me 😉

I will have a look.

Signed-off-by: Simon Kaufmann <simon.kfm@googlemail.com>
Signed-off-by: Simon Kaufmann <simon.kfm@googlemail.com>
Simon Kaufmann added 2 commits March 28, 2018 16:29
Signed-off-by: Simon Kaufmann <simon.kfm@googlemail.com>
Signed-off-by: Simon Kaufmann <simon.kfm@googlemail.com>
Copy link
Contributor

@maggu2810 maggu2810 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will have a look at the code the next days. Only a few comments.

<label>Text value</label>
<config-description>
<parameter name="stateTopic" type="text">
<label>Mqtt state topic</label>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add this comment only once, but will be true for all other occurrences: It should be written MQTT in labels and descriptions.

</parameter>
<parameter name="isfloat" type="boolean">
<label>Is Decimal?</label>
<description>Any supported transformation can be used. An example for a received JSON from a Mqtt state topic would be a pattern of JSONPATH:$.device.status.temperature for a json {device: {status: { temperature: 23.2 }}}.</description>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wrong description

org.eclipse.smarthome.io.transport.mqtt,
org.osgi.framework,
org.osgi.service.component,
org.osgi.service.component.annotations;resolution:=optional,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That line has been removed on another PR from all bundles

Service-Component: OSGI-INF/*.xml
Export-Package: org.eclipse.smarthome.binding.mqtt.generic,
org.eclipse.smarthome.binding.mqtt.generic.handler
Bundle-ActivationPolicy: lazy
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, as it is only necessary for the Eclipse IDE and not pure OSGi, I don't think we need it.
Is there already an agreement, if this should be used or not?


## Supported Channels

* **text**: This channel can show the received text on the given topic and can send text to a given topic.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about "percentage"


## Discovery

Broker connections are automatically discovered and presented as bridges. MQTT provides no means to enumerate available topics though, therefore things representing topics cannot be automatically discovered.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because broker connections are not managed by this binding, ...

### Common channel configuration parameters

* __mqttstate__: The MQTT topic that represents the state of the thing. This can be empty, the thing channel will be a state-less trigger then.
* __mqttcommand__: The MQTT topic that commands are send to. This can be empty, the thing channel will be read-only then.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mqttcommand => commandTopic?


* __mqttstate__: The MQTT topic that represents the state of the thing. This can be empty, the thing channel will be a state-less trigger then.
* __mqttcommand__: The MQTT topic that commands are send to. This can be empty, the thing channel will be read-only then.
* __transformpattern__: An optional transformation pattern like [JSONPath](http://goessner.net/articles/JsonPath/index.html#e2). Use http://jsonpath.com/ to verify your pattern for the latter case. An example would be JSONPATH:$.device.status.temperature for a received json input of `{device: {status: { temperature: 23.2 }}}` to extract the temperature value.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

transformpattern => transformationPattern?


### Common channel configuration parameters

* __mqttstate__: The MQTT topic that represents the state of the thing. This can be empty, the thing channel will be a state-less trigger then.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mqttstate => stateTopic?

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

two spaces for one indentation in pom files

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The formatter turns them into tabs 😕

Signed-off-by: Simon Kaufmann <simon.kfm@googlemail.com>
Copy link
Contributor

@maggu2810 maggu2810 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't new classes be annotated with NonNullByDefault?

*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.smarthome.binding.mqtt.generic;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know that the archetype generates a constant class in that package, but are this constants exported by intention?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess the initial intention was to have access to the BINDING_ID final string and all defined thing/bridge types. All my submissions so far have the constants class public.

@Override
public void processMessage(String topic, byte[] payload) {
if (!topic.equals(stateTopic)) {
logger.trace("Received Mqtt data on {}. Does not match configured thing {}", topic, stateTopic);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mqtt => MQTT
Please have a look at the other files, too.

.getTransformationService(transformationServiceName);
if (service == null) {
logger.warn("Transformation service '{}' not found", transformationServiceName);
return;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the state update on a missing but defined transformation service be ignored or should the state set to UNDEF?

Copy link
Contributor Author

@sjsf sjsf Mar 29, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[EDIT] right answer, wrong line. Here the updated one:

Yes, in this case I'd agree 😄

try {
str = service.transform(transformationPattern, str);
} catch (TransformationException e) {
logger.error("Error executing the transformation {}", str);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't UNDEF make sense for a failed transformation?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, I don't think so. I know several devices/services which send partial updates only. That would mean the state would stay in UNDEF most of the time.

Of course, this heavily depends on the semantics of a "missing" json attribute. Normally that would be the equivalent of null, but my feeling is that this is different in practice.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we cache the transformation service like it was done before? If we receive a message per second, we need to resolve the service 60 times a minute. That sounds very inefficient and contradicts my original design to be honest.

return stateTopic;
}

void dispose() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume the naming has been chosen because it is called by the handlers dispose method.
But a name should not chose the naming because of the context it is used only. Wouldn't it make sense to use a consistent naming (initialize and dispose or start and stop or open and close, ...).

* We make this an explicit function for test mocks.
*/
protected MqttBrokerConnectionHandler getBridgeHandler() {
return (MqttBrokerConnectionHandler) getBridge().getHandler();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getBridge() can return null, can't it?

config.value = new OnOffValue(config.on, config.off, config.inverse);
break;
default:
throw new IllegalArgumentException("ThingTypeUID not recognised");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't it about a ChannelTypeUID?

/**
* Returns the current value state.
*/
public State getValue();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

interface methods are pulic implicit

* Updates the internal value state with the given command.
*
* @param command The command to update the internal value.
* @return A string representation of the value to be send to Mqtt.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing doc for exception

/**
* Updates the internal value with the received Mqtt value.
*
* @param updatedValue
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing doc for return value and exception

*
* @author David Graeff - Initial contribution
*/
public class ChannelConfig implements MqttMessageSubscriber {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yes I remember, that was a proof of concept class. It works, but configuration holder classes should be pure DTOs. It should read like this: MessageSubscriber extends ChannelConfig implements MqttMessageSubscriber

@davidgraeff
Copy link
Contributor

@maggu2810 : I have squashed the PR and incorporated your requested changes in a second commit in #5450

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants