-
Notifications
You must be signed in to change notification settings - Fork 784
Conversation
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>
Signed-off-by: Simon Kaufmann <simon.kfm@googlemail.com>
There was a problem hiding this 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; |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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) -> { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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.
@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>
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 this adoption already happened, see: https://github.com/eclipse/smarthome/pull/5101/files#diff-1000633721a6b82ab70752c8c3124aefR22 |
Perfect. There is one thing missing. I have asked the related question here: https://community.openhab.org/t/development-is-it-possible-in-a-thing-types-xml-for-a-thing-to-refer-to-bridges-defined-in-some-other-binding/42126 |
...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. |
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. |
Argh, sure! |
Signed-off-by: Simon Kaufmann <simon.kfm@googlemail.com>
Are dots not allowed in binding directory names? The "mqtt.generic" name wasn't too bad. |
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? |
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>
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 Let's move/fuse binding.xml, README and the like later on. It's already chaotic enough... |
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:
|
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>
Signed-off-by: Simon Kaufmann <simon.kfm@googlemail.com>
Signed-off-by: Simon Kaufmann <simon.kfm@googlemail.com>
There was a problem hiding this 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> |
There was a problem hiding this comment.
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> |
There was a problem hiding this comment.
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, |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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"?> |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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>
There was a problem hiding this 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; |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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() { |
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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"); |
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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
@maggu2810 : I have squashed the PR and incorporated your requested changes in a second commit in #5450 |
This is a rebased version of #3876 with commits on top which
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