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

serial: add serial transport API and implementations #5313

Merged
merged 5 commits into from
Apr 4, 2018
Merged

serial: add serial transport API and implementations #5313

merged 5 commits into from
Apr 4, 2018

Conversation

maggu2810
Copy link
Contributor

As a framework we should not depend on a special serial transport implementation.
I started a thin abstract layer between the specific implementation and its usage.

There is an API bundle that defines the interface but is not bound to any specific implementation.
Using OSGi service dependency injection this is the only package that needs to be referenced and the consumer does not need to know the specific implementation.

I also added two implementations wrappers. One for "gnu.io" and one for "javax.comm".


There is a "org.eclipse.soda.dk.comm" implementation that uses "javax.comm" and is the only serial port communication I know that is licensed using the EPL.
So, I really prefer to leave it to solutions to choose the implementation for serial access and support a easily switch between e.g. nrjavaserial and that one.

@kaikreuzer
Copy link
Contributor

kaikreuzer commented Mar 26, 2018

~~Isn't this a duplicate of #4465?~~~

Edit: Sorry, it is still too early in the morning and I am tired... I only saw now that this isn't an issue, but actually a PR - so ignore all my comments...

is the only serial port communication I know that is licensed using the EPL.

No, see #4465 (comment).

Edit: Ok, you were speaking about an implementation - right, this seems to be the only option with EPL.

kaikreuzer pushed a commit that referenced this pull request Mar 27, 2018
Add the SODA's Device Kit implementation that is part of the Eclipse
Kura project and provides an EPL based serial communication library.

Related to: #5313
Signed-off-by: Markus Rathgeb <[email protected]>
Copy link
Contributor

@kaikreuzer kaikreuzer left a comment

Choose a reason for hiding this comment

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

Thanks @maggu2810, that's an excellent work!
I haven't yet actively tested the code and I am astonished that in the end rxtx and javax.comm are so similar that you were able to put a facade on top of both.
Just out of curiosity, did you check current bindings in openhab2-addons whether they use any more specific rxtx features that might not be possible to use through this facade? If so, I don't have an issue if they have to stick to rxtx directly, so no worries.

My only wish would be to rename the bundles, trying to limit the number of dots in their names...


<name>Eclipse SmartHome Serial Transport for javax.comm</name>

<!-- BEG: workaround until the respective three bundles has been added to our third party p2 repo -->
Copy link
Contributor

Choose a reason for hiding this comment

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

workaround can be removed now

Bundle-ManifestVersion: 2
Bundle-Name: Eclipse SmartHome Serial Transport API
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Bundle-SymbolicName: org.eclipse.smarthome.io.transport.serial.api;singleton:=true
Copy link
Contributor

Choose a reason for hiding this comment

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

I would prefer org.eclipse.smarthome.io.transport.serial as a bundle + package name - we usually never use api in such situations.

</parent>

<groupId>org.eclipse.smarthome.io</groupId>
<artifactId>org.eclipse.smarthome.io.transport.serial.gnu.io</artifactId>
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe rename to org.eclipse.smarthome.io.transport.serial.rxtx?

</parent>

<groupId>org.eclipse.smarthome.io</groupId>
<artifactId>org.eclipse.smarthome.io.transport.serial.javax.comm</artifactId>
Copy link
Contributor

Choose a reason for hiding this comment

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

Maybe rename to org.eclipse.smarthome.io.transport.serial.javaxcomm?

} else {
return null;
}
// return getIdentifiers().filter(id -> id.getName().equals(name)).findFirst().orElse(null);
Copy link
Contributor

Choose a reason for hiding this comment

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

can this be removed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hm, I am not lucky about that.

There is a problem caused by our usage of the nullness annotations and the miss of EEAs.

The interface is marked as "non null by default".
The optional type is a generic one.
The type parameter T of Optional<T> is set to SerialPortIdentifier (the variable opt is of type Optional<SerialPortIdentifier>).
As types are now default non null, the tooling consider T as @NonNull SerialPortIdentifier.

The optional's method orElse looks like public T orElse(T other) (see Optional API).
Now, the tooling things the method looks like public @NonNull SerialPortIdentifier orElse(@NonNullSerialPortIdentifier other).
But that is not correct: "other - the value to be returned if there is no value present, may be null".

So, the EEA needs to state, that regardless if T is nullable or not the method looks like:

public @NonNull T orElse(@Nullable T other);

So, instead of using the method that is part of the Java API and does exactly what we want, we have to write other code just to fulfill the tooling (or better "to workaround a wrong tooling behavior").

@kaikreuzer
Copy link
Contributor

You might also want to add something to the docs, at least add the io.transport.serial package to https://github.com/eclipse/smarthome/blob/master/docs/documentation/development/bindings/dependencies.md#eclipse-smarthome-packages.

@maggu2810
Copy link
Contributor Author

maggu2810 commented Mar 27, 2018

I am astonished that in the end rxtx and javax.comm are so similar that you were able to put a facade on top of both.

Please have a look at https://en.wikibooks.org/wiki/Serial_Programming/Serial_Java#RxTx_2

If something has been written against java(x).comm it should nowadays be enough to replace the one package with the other one (gnu.io).

That has also been the reason why I did not choose rxtx but gnu.io as other implementations should (IMHO) be compatible, too. I can have a look at.
And it has been the reason I choose javax.comm and not javacomm -- it would without the dot make more sense to drop the "x" too. I have choosen the package name and the other one would be refering to a specific implementation.
If we choose rxtx we have also to use dkcomm and perhaps other ones for other implementations -- but they should be equal if they implement the Java Communication API (just differ in its package name).

-- edit begin --

Thinking about the implementation postfixes / names again:
We could also choose the .javacomm for all implementations that are using the javax.comm package and .rxtx for all implementations that are using the gnu.io package. In both case there are different implementations but that has been (if my reading has been correct) the first ones that uses that packages (for Java Communication API they are pretty sure the first one, and for RxTx I believe so).

-- edit end --

One comment about the ".api" postfix.
I also considered about and think there is a small difference.
Our current bundles mix the API and the implementation, so ".api" for that bundles would be not correct. Also other widely used bundles contains the API and e.g. the reference implementation and so API is not used in that names.
I would like to point out, that this bundle is really about the API only.
If you would like to provide an implementation, you have to provide that API.
If you want to use that API and not depend on a specific implementation, you have to implement against that API only.
The postfix itself is surely not a must, but IMHO in this case (and if we ever want to provide API only bundles for other topics, too) is a good marker.

@maggu2810
Copy link
Contributor Author

If so, I don't have an issue if they have to stick to rxtx directly, so no worries.

Sure, keeping RxTx directly is still possible. But we should perhaps try to use the newly interface (and implementations) if possible.

Just out of curiosity, did you check current bindings in openhab2-addons whether they use any more specific rxtx features that might not be possible to use through this facade?

I didn't check the other addons yet. I tested the recent proprietary EnOcean binding implementation (just a little bit) and it seems to do its job.
The real I/O is done using the input and output streams, so this should work for the other bindings, too and set baudrate etc. is already present.

If there are other methods needed, we could add them later easily.

Are there special addons that you would be happy if I could have a look at.
As I would like to get this feature in and that it fulfills our needs, I can have a look at one or two of them (perhaps the one that handles the most communication -- Z-Wave?).

@kaikreuzer
Copy link
Contributor

Please have a look at https://en.wikibooks.org/wiki/Serial_Programming/Serial_Java#RxTx_2

Thanks, an interesting Java Serial history lesson :-)

We could also choose the .javacomm for all implementations that are using the javax.comm package and .rxtx for all implementations that are using the gnu.io package.

I'd prefer this naming. At the moment, we otherwise have two different semantics of the "dots": Package/bundle hierarchy on the one hand and external package name characters on the other hand - I prefer to keep it for hierarchy usage only and thus I'd hope for names that only introduce a single sub-name behind transport.serial.

I would like to point out, that this bundle is really about the API only.
If you would like to provide an implementation, you have to provide that API.

Strictly speaking, wouldn't his part rather be an spi?

I think we should not really have api/spi in the package names at all - after all, we are using OSGi and we have defined "internal" packages that are not exposed and that all other packages are exported and thus are automatically an api/spi. For a more detailed information for implementors, I would rather suggest to follow up on #4736 (@SJKA feel free to comment as well).

Sure, keeping RxTx directly is still possible. But we should perhaps try to use the newly interface (and implementations) if possible.

Yes, fully agree. Once this is merged, I expect every new contribution on ESH and openHAB side to use the ESH API and not RXTX directly.

Are there special addons that you would be happy if I could have a look at.

No, I don't have anything specific in mind right now.

@maggu2810 maggu2810 changed the title RFC: serial: add serial transport API and implementations serial: add serial transport API and implementations Mar 29, 2018
@maggu2810
Copy link
Contributor Author

Remaining tasks:

  • fix the features
  • add new features

Remaining parts should be addressed.

Will give a ping as soon as finished.

* @author Markus Rathgeb - Initial contribution
*/
@NonNullByDefault
public interface SerialPortEvent {
Copy link
Contributor

Choose a reason for hiding this comment

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

Wouldn't it make more logical if we provide an enum instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hi @amitjoy,
the intention of this serial transport has been to provide a thin abstraction layer for the specific implementations. There is the Java Communications API and that API is using the package javax.comm. There has also been RXTX that provides (v2.1 upwards) that but using another package only (gnu.io).
The most implementations I know about support that official API under one of that packages.
To keep the abstraction layer thin I would like to keep that numbers as in the "usual" implementations.
Sure, we could create an enum and assign an internal number to that enumerations but then we need to translate from enum to int on every call and from int to enum on every return where that values will be used (e.g. method that will be added on request).

That is the background...
Should we really add the overhead at runtime just to have enums instead of int and to be strictly speaking just to have some stronger type constraints?

Copy link
Contributor

Choose a reason for hiding this comment

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

From the coding perspective, it would be more readable to have an enum instead. But I feel, the design perspective is different in this scenario. Yes, you are right. It would be a bit of overhead since it will always be a runtime dynamic execution. You can leave it as it is.

* @author Markus Rathgeb - Initial contribution
*/
@NonNullByDefault
public interface SerialPortEventListener {
Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Hm, you are right, this interface currently has only one function, so it could be a good candidate.
But personally I don't like to mark a listener as a function interface because it is about a special interface what a listener has to be provide and that could vary.

See also the [documentation of the functional interface annotation]:

If a type is annotated with this annotation type, compilers are required to generate an error message unless:

and

However, the compiler will treat any interface meeting the definition of a functional interface as a functional interface regardless of whether or not a FunctionalInterface annotation is present on the interface declaration.

The only real "win" is to get a compiler error if someone adds another function to that interface.
But if we ever want another function in that interface we will do it and have to remove that annotation again.
So, what is the big win to add it now?

[documentation of the functional interface annotation]: However, the compiler will treat any interface meeting the definition of a functional interface as a functional interface regardless of whether or not a FunctionalInterface annotation is present on the interface declaration.

Copy link
Contributor

Choose a reason for hiding this comment

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

You are absolutely right. From the provider perspective it doesn't add real value but from the consumer's perspective, it does. The annotation benefits the provider because it will generate a compile error if the interface is not functional - and it benefits the consumer because it documents that the interface is meant to be used in lambdas. That's the only benefit. I am leaving it to you now.

*
* @param owner name of the owner that port should be assigned to
* @param timeout time in milliseconds to block waiting for opening the port
* @return a serial port
Copy link
Contributor

@amitjoy amitjoy Mar 29, 2018

Choose a reason for hiding this comment

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

@throws NPE or annotations on owner

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Have you realized that the whole class is annotated by NonNullByDefault? No need to add the NonNull annotation to any members, only Nullable would be needed.

Copy link
Contributor

Choose a reason for hiding this comment

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

Didn't notice at all. Thanks for the clarification.

} else {
return null;
}
// return getIdentifiers().filter(id -> id.getName().equals(name)).findFirst().orElse(null);
Copy link
Contributor

Choose a reason for hiding this comment

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

I still prefer the commented way

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Me, too and that has been the reason I didn't delete that line but comment it until we ever have an agreement about EEAs (if you are interested in have a look at the other issues and PRs).
Here the reason why it is this way ATM: #5313 (comment)

* @param listener the listener
* @throws TooManyListenersException if too many listeners has been added
*/
void addEventListener(SerialPortEventListener listener) throws TooManyListenersException;
Copy link
Contributor

Choose a reason for hiding this comment

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

annotation on listener denoting it cannot be null

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Have you realized that the whole class is annotated by NonNullByDefault? No need to add the NonNull annotation to any members, only Nullable would be needed.

Copy link
Contributor

Choose a reason for hiding this comment

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

Didn't notice that at all. Sorry for the noise. 👍

* @param timeout
* @throws UnsupportedCommOperationException if the operation is not supported
*/
void enableReceiveTimeout(int timeout) throws UnsupportedCommOperationException;
Copy link
Contributor

Choose a reason for hiding this comment

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

@throws IllegalArgumentException if {@code timeout} is less than zero

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I will have at the respective implementations (see comment above) about their documentation and implementation. After that I will add a documentation and catch an invalid input in the abstraction layers or throw an exception.
Thanks for the hint.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

  • nrjavaserial throws an IAE on a negative value
  • dkcomm does nothing on a negative value
  • purejavacomm throws an IAE on a negative value
  • jSerialComm does not support that operation, so a wrapper should throw an UnsupportedCommOperationException takes the negative value, but I cannot find at which place the value is used
  • serial.io I do not have access to the sources, the JavaDoc does not state how a negative value is handled
  • java-simple-serial-connector the input and output stream handling needs to be wrapped by the "wrapper implementation" also that function, 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.

Let's define that an IAE should be thrown.
I changed the documentation of the interface and fixed the wrapper implementations.

Signed-off-by: Markus Rathgeb <[email protected]>
Signed-off-by: Markus Rathgeb <[email protected]>
@maggu2810
Copy link
Contributor Author

@kaikreuzer IMHO it is ready for a final look at...

@kaikreuzer kaikreuzer self-assigned this Apr 3, 2018
@@ -304,7 +304,22 @@

<feature name="esh-io-transport-serial" version="${project.version}">
Copy link
Contributor

Choose a reason for hiding this comment

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

Just for my understanding: This feature can now be regarded as legacy and solutions should always either use esh-io-transport-serial-javacomm or esh-io-transport-serial-rxtx instead, is this correct?
If so, I wouldn't mind to completely remove it - for me there isn't any need for this to be backward compatible.

Copy link
Contributor Author

@maggu2810 maggu2810 Apr 3, 2018

Choose a reason for hiding this comment

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

Bundles needs the serial API and "some" implementation.
So, features that require the serial transport depends on esh-io-transport-serial.
If no implementation is already installed it falls back to esh-io-transport-serial-rxtx and install also this one.
If the dependencies are already satisfied, because a product or user installs esh-io-transport-serial-javacomm the dependencies of esh-io-transport-serial are already installed and esh-io-transport-serial-rxtx is not installed.

We depend on an unspecific esh-io-transport-serial feature, that also fallback to a specific implementation if one is missing.

Have you a better idea how to model the unspecific dependency to "any" implementation?

AFAIK it is modeled similar in upstream Karaf for e.g. a "http" implementation that just defaults to the "jetty" one.

Copy link
Contributor

Choose a reason for hiding this comment

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

If the dependencies are already satisfied, because a product or user installs esh-io-transport-serial-javacomm the dependencies of esh-io-transport-serial are already installed

I still haven't fully grasped the Karaf feature dependency management here - sorry, if I ask stupid questions.
But esh-io-transport-serial defines esh-io-transport-serial-rxtx as its dependency, so how can this be already installed, if a user only installed esh-io-transport-serial-javacomm? To me, it appears as if esh-io-transport-serial is tied to rxtx and not being unspecific (while the previous "require feature=serial" looked pretty unspecific).

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 feature resolution is done using OSGi requirements and capabilities.
All the ones in the manifests of the bundles are taken into account and also that ones that are added to the feature itself.
So, package imports and exports define requirements and capabilities and also the explicit provide capability and require capability ones are taken into account.
As bundles sometimes miss that information it could be added to the feature, too (as it is sometimes not possible to modify the bundles).
We also define a lot of capabilities in the features instead of adding that to the bundles...

If a feature or a dependency is using the attribute dependency="true" it will be installed only if there are requirements that are not satisfied without that one and satisfied with that one.

If a bundle requires an OSGi service that implements a specific interface the bundle should contain a specific require capability section.
If a bundle contains an OSGi service that implements a specific interface the bundel should contain a specific provide capability section.
Here you point me with your comment to the situation that our manifest misses the information (e.g. the serialbutton binding) that it needs such a service implementation ... I added the requirement for a specific implementation to the unspecific serial feature now.

So, features itself that needs a serial implementation should depend on "...-serial" that is does not specify the implementation itself. It only brings in one using dependency="true" if the capability is not already satisfied.

If an user install before or later a feature that brings in the requirement, the fallback feature can be dropped again, if that feature is removed another time the fallback will be used again...

We already use the dependency attribute for the two different Guice bundles, so the bigger one is only installed if required (and the smaller one could be dropped if the bigger one is installed).

Copy link
Contributor

Choose a reason for hiding this comment

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

Ok, I think I got it. Many thanks for the elaborate explanation!

Signed-off-by: Markus Rathgeb <[email protected]>
@kaikreuzer kaikreuzer merged commit 8e3cf68 into eclipse-archived:master Apr 4, 2018
@maggu2810 maggu2810 deleted the serial-transport branch April 4, 2018 09:09
@kaikreuzer
Copy link
Contributor

@maggu2810 Latest openHAB distro with this change now logs

23:38:53.587 [ERROR] [eclipse.smarthome.io.transport.serial] - Component descriptor entry 'OSGI-INF/*.xml' not found

at startup. Did we miss anything?

@maggu2810
Copy link
Contributor Author

maggu2810 commented Apr 8, 2018

I assume it is called by the line Service-Component: OSGI-INF/*.xml of the serial bundle and the fact, that there will be no component in the OSGI-INF directory.
I will come with a fix as soon as I can change and test (I assume except the warning, no runtime behavior is affected).

@openhab-bot
Copy link
Contributor

This pull request has been mentioned on openHAB Community. There might be relevant details there:

https://community.openhab.org/t/2-3-0-snapshot-1250-classicui-essentially-blank/43252/10

@openhab-bot
Copy link
Contributor

This pull request has been mentioned on openHAB Community. There might be relevant details there:

https://community.openhab.org/t/openhab-2-3-problem-with-serial-binding-dependency/45715/5

@htreu htreu added this to the 0.10.0 milestone Oct 30, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants