Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[modbus] Modbus transport API simplification #7994

Merged
merged 57 commits into from
Jul 10, 2020

Conversation

ssalonen
Copy link
Contributor

@ssalonen ssalonen commented Jun 23, 2020

Co-authored with @mrbig, user feedback from @Rossko57 .

As discussed in openhab/openhab-core#1435 (comment).

This is a rather large PR, with the aim of improving the modbus transport API developer experience based on feedback from @mrbig and sunspec bundle.

The main changes introduced:

  1. callbacks are @Functional, allowing binding developer to use lambdas and much less verbose way to access the data read. The API is vaguely modeled after vertx AsyncResult: https://vertx.io/docs/apidocs/io/vertx/core/AsyncResult.html
comms.submitOneTimePoll(request, result -> {
            if (result.hasError()) {
                Exception error = (@NonNull Exception) result.getCause();
                handleError(error);
            } else {
                ModbusRegisterArray registers = (@NonNull ModbusRegisterArray) result.getRegisters();
                modelBlockReceived(registers);        
            });

Previously one had to have callback with three methods, but depending on the type of request, only two methods are actually applicable.

BasicModbusReadRequestBlueprint request = new BasicModbusReadRequestBlueprint(slaveId,
        ModbusReadFunctionCode.READ_MULTIPLE_REGISTERS, baseAddress, // Start address
        count,
        maxTries);
PollTask task = new BasicPollTaskImpl(endpoint, request, new ModbusReadCallback() {

    @Override
    public void onRegisters(ModbusReadRequestBlueprint request, ModbusRegisterArray registers) {
        handleReceived(registers);
    }

    @Override
    public void onError(ModbusReadRequestBlueprint request, Exception error) {
        handleError(error);             
    }

    @Override
    public void onBits(@Nullable ModbusReadRequestBlueprint request, @Nullable BitArray bits) {
        // this is never called by transport bundle since request is for reading registers, not "bits" 
    }
});
handler.getManagerRef().get().submitOneTimePoll(task);
  1. The other change is that transport API calls take in relevant parameters directly, and it's not necessary to "bundle" arguments into a BasicPollTaskImpl . See example above
  2. There were many java interfaces which had only one implementation. We have removed the interfaces and use the implementation class in many places. There were also trivial implementations which did not really add value (e.g. specialized bit array implementation for cases with single bit) I think this reduces the cognitive load when getting familiar with the transport bundle. Commits can be recognized with "abstractions removed"

The above changes are not introducing any changes in behaviour.


There is also an API change which is changing slightly how to interact with transport API: introduction of ModbusCommunicationInterface.

Now, the transport API consumers need to "open" a ModbusCommunicationInterface to interact with a particular Modbus server ("slaves"). Modbus slaves are identified by tcphost:tcpport or serialport. Once you have been finished interacting with the slave, you close() the communication interface. There can be many communication interfaces open to the same slave.

Closing the communication interface signals to transport bundle that you are not expecting any further requests for a while. The transport bundle automatically frees resources (e.g. disconnects TCP connections) immediately if the last communication interface pointing to a server is closed.

The eager cleanup of resources was one of the main motivations for this change. In addition, some of the API details became simpler as well (e.g. error handling when there are conflicting baud rates trying to be associated with the same serial port).

In practice, the communication interface is held by the thing representing the modbus slave ("tcp" or "serial" thing). Once the thing is disposed, also the communication interface is closed.


Third change is improving automatic disconnection of unused idle connections. Previously connection age was checked after every request, and disconnected if older than the configured value. This had the downside that connection pool can contain open old connections when there are no transactions.

Now, the connection pool is inspected every 10 seconds, and old idle connections are disconnected.

For the user this is quite a transparent change, and should just mean that openHAB is not keeping any connections for long periods of time.

@ssalonen ssalonen added the work in progress A PR that is not yet ready to be merged label Jun 23, 2020
@ssalonen ssalonen requested a review from a team as a code owner June 23, 2020 17:55
@ssalonen
Copy link
Contributor Author

Prebuilt jars for commit 9a65a4a (June 23, 2020)
snapshot-commit-9a65a4abe2e9845b351e5adcc569f6a2bb50e2c7.zip

@TravisBuddy
Copy link

Travis tests have failed

Hey @ssalonen,
please read the following log in order to understand the failure reason. There might also be some helpful tips along the way.
It will be awesome if you fix what is wrong and commit the changes.

1 similar comment
@TravisBuddy
Copy link

Travis tests have failed

Hey @ssalonen,
please read the following log in order to understand the failure reason. There might also be some helpful tips along the way.
It will be awesome if you fix what is wrong and commit the changes.

@TravisBuddy
Copy link

Travis tests have failed

Hey @ssalonen,
please read the following log in order to understand the failure reason. There might also be some helpful tips along the way.
It will be awesome if you fix what is wrong and commit the changes.

1 similar comment
@TravisBuddy
Copy link

Travis tests have failed

Hey @ssalonen,
please read the following log in order to understand the failure reason. There might also be some helpful tips along the way.
It will be awesome if you fix what is wrong and commit the changes.

@ssalonen ssalonen changed the title [WIP] [modbus] Modbus transport API simplification [modbus] Modbus transport API simplification Jun 24, 2020
@ssalonen ssalonen removed the work in progress A PR that is not yet ready to be merged label Jun 24, 2020
@TravisBuddy
Copy link

Travis tests were successful

Hey @ssalonen,
we found no major flaws with your code. Still you might want to look at this logfile, as we usually suggest some optional improvements.

@fwolter
Copy link
Member

fwolter commented Jun 24, 2020

comms.submitOneTimePoll(request, result -> {
            if (result.hasError()) {
                Exception error = (@NonNull Exception) result.getCause();
                handleError(error);
            } else {
                ModbusRegisterArray registers = (@NonNull ModbusRegisterArray) result.getRegisters();
                modelBlockReceived(registers);        
            });

I think error handling is mandatory. Why not adding a second functional interface to submitOneTimePoll() handling the error case?

comms.submitOneTimePoll(request, this::handleError, result -> {
...

@ssalonen
Copy link
Contributor Author

@fwolter very valid suggestion! With @mrbig we tried to model other similar APIS which bundle errors and valid results into same object

We played with several different API designs but to be honest, separating error from the successful result was not one of them.

By separating the error case the API consumer can avoid an if statement. On the other hand, bundling error case to the same result object makes it clear when the operation has ended (either successfully or with error). Are there any other upsides/downsides?

Either way will certainly work. Any thoughts @mrbig ?

I am not too keen on changing this if the gained benefit is small - it is somewhat large change considering the large footprint of the tests.

@fwolter
Copy link
Member

fwolter commented Jun 26, 2020

Handling result and exception paths separately reflects OOP paradigm. You don't need the casting and the getCause() invocation. Furthermore, the compiler complains, if the user forgets to handle the error.

I can't follow your argument. not being clear when the operation finished. You could specify either the error handler or the result handler is invoked, when the operation finished.

@ssalonen
Copy link
Contributor Author

@fwolter I like your thinking. I think it's especially valuable that you have to handle the errors and decide what to do with them.

Signed-off-by: Sami Salonen <ssalonen@gmail.com>
Signed-off-by: Sami Salonen <ssalonen@gmail.com>
Signed-off-by: Sami Salonen <ssalonen@gmail.com>
Signed-off-by: Sami Salonen <ssalonen@gmail.com>
Signed-off-by: Sami Salonen <ssalonen@gmail.com>
Signed-off-by: Sami Salonen <ssalonen@gmail.com>
Signed-off-by: Sami Salonen <ssalonen@gmail.com>
Signed-off-by: Sami Salonen <ssalonen@gmail.com>
Signed-off-by: Sami Salonen <ssalonen@gmail.com>
Signed-off-by: Sami Salonen <ssalonen@gmail.com>
Signed-off-by: Sami Salonen <ssalonen@gmail.com>
Signed-off-by: Sami Salonen <ssalonen@gmail.com>
Signed-off-by: Sami Salonen <ssalonen@gmail.com>
Signed-off-by: Sami Salonen <ssalonen@gmail.com>
Signed-off-by: Sami Salonen <ssalonen@gmail.com>
Signed-off-by: Sami Salonen <ssalonen@gmail.com>
Signed-off-by: Sami Salonen <ssalonen@gmail.com>
Signed-off-by: Sami Salonen <ssalonen@gmail.com>
@ssalonen ssalonen requested a review from cpmeister July 6, 2020 08:37
@ssalonen
Copy link
Contributor Author

ssalonen commented Jul 6, 2020

@mrbig (huge thanks! 👏 ) & me worked on this more and now errors are propagated via their own callback. We also use Optionals to wrap the data in callbacks

New review welcome!

started);
try {
executeOperation(task, false, pollOperation);
} catch (RuntimeException e) {
Copy link
Contributor Author

@ssalonen ssalonen Jul 6, 2020

Choose a reason for hiding this comment

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

Cherry-picked 5bde9d6 during rebase in this commit.

@TravisBuddy
Copy link

Travis tests were successful

Hey @ssalonen,
we found no major flaws with your code. Still you might want to look at this logfile, as we usually suggest some optional improvements.

1 similar comment
@TravisBuddy
Copy link

Travis tests were successful

Hey @ssalonen,
we found no major flaws with your code. Still you might want to look at this logfile, as we usually suggest some optional improvements.

Signed-off-by: Sami Salonen <ssalonen@gmail.com>
Signed-off-by: Sami Salonen <ssalonen@gmail.com>
@TravisBuddy
Copy link

Travis tests were successful

Hey @ssalonen,
we found no major flaws with your code. Still you might want to look at this logfile, as we usually suggest some optional improvements.

@cpmeister cpmeister merged commit 350ee95 into openhab:2.5.x Jul 10, 2020
@cpmeister cpmeister added this to the 2.5.7 milestone Jul 10, 2020
@cpmeister cpmeister added the enhancement An enhancement or new feature for an existing add-on label Jul 10, 2020
knikhilwiz pushed a commit to knikhilwiz/openhab2-addons that referenced this pull request Jul 12, 2020
* [modbus] connection closing behaviour finetuned

The binding closes TCP/serial connections connections as per user
configuration. It is either
- every time, immediately after a transaction (reconnectAfterMillis=0)
- *after* a read/write transaction, if the connection has been open "too
  long" (configurable with reconnectAfterMillis)

We have an obvious downside to this simple logic -- the connection
can remain open "indefinitely" if there are no transactions occurring.
With Modbus we are quite often dealing with PLCs and other embedded
systems with limited resources, and "reserving" the connection is not
always something we want.

Previously (2.5.x branch) connections were also closed when
- a regular poll task was unregistered (i.e. we stop reading regularly
from a modbus server ("slave"). Since most users have regular polling in
place, so this ensured that connections do get closed.

In this commit, the behaviour is adjusted such that connections are
closed when last communication interface pointing to the server/serial
port (i.e. "endpoint") is close()'d.

With modbus binding this basically means when the tcp or serial thing
is removed / disabled, the connections is closed/freed, but only if it
is the last thing pointing to that server or serial port.

Since modbus.sunspec binding reuses modbus serial & tcp endpoint things,
the same note applies for modbus.sunspec binding.

This is change in functional behaviour but in a way is logical. We can
further introduce to have "delayed"/"deferred"/"debounce" connection
closing connections as per the setting reconnectAfterMillis even in
situations where communication interface is still open.

* [modbus] Check and disconnect idle connections without transactions
* [modbus] mvn spotless:apply
* [modbubs] Fixed log message
* [modbus] Race condition fix

The CountDownLatch was used as a guard (latch.await) in many tests to
wait for callbacks to be called before proceeding with assertions.

Since the latch was countDown() beginning of the callback, we introduced
a race condition with the subsequent assertions and updating the other
counters used in the subsequent assertions.

This commit updates the CountDownLatch as the last step of the callback,
resolving the race condition.

* [modbus] small test fix
* [modbus] readcallback changed to nonnull
* [modbus] Refactored ModbusCommunicationInterface to have seperate callback for result and failure

However I had to dig deep to reach all the affected parts.
Also there is a new callback, and a new "result" type to communicate
the failures.
* [modbus] SmokeTest refactored to new api
* [modbus] Modbus bundle refactored to use the new api
* [modbus][sunspec] refactored sunspec bundle to use the new modbus API
* [modbus] refactor modbus tests to use the new api
* [modbus] Removed ModbusWriteCallback interface from ModbusDataThingHandler
Also reset ModbusDataThingHandler testWriteHandling generic to it's original form
* [modbus] ModbusDataThingHandler does not implement ModbusReadCallback anymore
Instead it has a public onReadResult method. ModbusPollerThingHandler changed to
work with ModbusDataThingHandler children.
* [modbus] Fixed caching in ModbusPollerThingHandler
* [modbus] read modbus data as Optionals
* [modbus] toString for PollResult
* [modbus] fixed confusing variable name
* [modbus] Disallow null callbacks
* [modbus] mvn spotless:apply
* [modbus] Removing Nullable decorations
* [modbus] submitOneTimeWrite simplification
* [modbus] Less verbose logging
* [modbus] submitOneTimePoll simplification
* [modbus] Less verbose logging
* [modbus] Many null warnings removed
* [modbus] Fix: no need for a @nonnull annotation because it is default
* [modbus] Removing unneeded Nullable, using final in immutables
* [modbus] Explicit functional interface
* [modbus] @nullable and @NonNullByDefault aligned with coding conventions
* [modbus] Collections.emptyMap instead of allocating new map every time.

Signed-off-by: Sami Salonen <ssalonen@gmail.com>

Co-authored-by: Nagy Attila Gábor <mrbig@sneaker.hu>
CSchlipp pushed a commit to CSchlipp/openhab-addons that referenced this pull request Jul 26, 2020
* [modbus] connection closing behaviour finetuned

The binding closes TCP/serial connections connections as per user
configuration. It is either
- every time, immediately after a transaction (reconnectAfterMillis=0)
- *after* a read/write transaction, if the connection has been open "too
  long" (configurable with reconnectAfterMillis)

We have an obvious downside to this simple logic -- the connection
can remain open "indefinitely" if there are no transactions occurring.
With Modbus we are quite often dealing with PLCs and other embedded
systems with limited resources, and "reserving" the connection is not
always something we want.

Previously (2.5.x branch) connections were also closed when
- a regular poll task was unregistered (i.e. we stop reading regularly
from a modbus server ("slave"). Since most users have regular polling in
place, so this ensured that connections do get closed.

In this commit, the behaviour is adjusted such that connections are
closed when last communication interface pointing to the server/serial
port (i.e. "endpoint") is close()'d.

With modbus binding this basically means when the tcp or serial thing
is removed / disabled, the connections is closed/freed, but only if it
is the last thing pointing to that server or serial port.

Since modbus.sunspec binding reuses modbus serial & tcp endpoint things,
the same note applies for modbus.sunspec binding.

This is change in functional behaviour but in a way is logical. We can
further introduce to have "delayed"/"deferred"/"debounce" connection
closing connections as per the setting reconnectAfterMillis even in
situations where communication interface is still open.

* [modbus] Check and disconnect idle connections without transactions
* [modbus] mvn spotless:apply
* [modbubs] Fixed log message
* [modbus] Race condition fix

The CountDownLatch was used as a guard (latch.await) in many tests to
wait for callbacks to be called before proceeding with assertions.

Since the latch was countDown() beginning of the callback, we introduced
a race condition with the subsequent assertions and updating the other
counters used in the subsequent assertions.

This commit updates the CountDownLatch as the last step of the callback,
resolving the race condition.

* [modbus] small test fix
* [modbus] readcallback changed to nonnull
* [modbus] Refactored ModbusCommunicationInterface to have seperate callback for result and failure

However I had to dig deep to reach all the affected parts.
Also there is a new callback, and a new "result" type to communicate
the failures.
* [modbus] SmokeTest refactored to new api
* [modbus] Modbus bundle refactored to use the new api
* [modbus][sunspec] refactored sunspec bundle to use the new modbus API
* [modbus] refactor modbus tests to use the new api
* [modbus] Removed ModbusWriteCallback interface from ModbusDataThingHandler
Also reset ModbusDataThingHandler testWriteHandling generic to it's original form
* [modbus] ModbusDataThingHandler does not implement ModbusReadCallback anymore
Instead it has a public onReadResult method. ModbusPollerThingHandler changed to
work with ModbusDataThingHandler children.
* [modbus] Fixed caching in ModbusPollerThingHandler
* [modbus] read modbus data as Optionals
* [modbus] toString for PollResult
* [modbus] fixed confusing variable name
* [modbus] Disallow null callbacks
* [modbus] mvn spotless:apply
* [modbus] Removing Nullable decorations
* [modbus] submitOneTimeWrite simplification
* [modbus] Less verbose logging
* [modbus] submitOneTimePoll simplification
* [modbus] Less verbose logging
* [modbus] Many null warnings removed
* [modbus] Fix: no need for a @nonnull annotation because it is default
* [modbus] Removing unneeded Nullable, using final in immutables
* [modbus] Explicit functional interface
* [modbus] @nullable and @NonNullByDefault aligned with coding conventions
* [modbus] Collections.emptyMap instead of allocating new map every time.

Signed-off-by: Sami Salonen <ssalonen@gmail.com>

Co-authored-by: Nagy Attila Gábor <mrbig@sneaker.hu>
Signed-off-by: CSchlipp <christian@schlipp.de>
MPH80 pushed a commit to MPH80/openhab-addons that referenced this pull request Aug 3, 2020
* [modbus] connection closing behaviour finetuned

The binding closes TCP/serial connections connections as per user
configuration. It is either
- every time, immediately after a transaction (reconnectAfterMillis=0)
- *after* a read/write transaction, if the connection has been open "too
  long" (configurable with reconnectAfterMillis)

We have an obvious downside to this simple logic -- the connection
can remain open "indefinitely" if there are no transactions occurring.
With Modbus we are quite often dealing with PLCs and other embedded
systems with limited resources, and "reserving" the connection is not
always something we want.

Previously (2.5.x branch) connections were also closed when
- a regular poll task was unregistered (i.e. we stop reading regularly
from a modbus server ("slave"). Since most users have regular polling in
place, so this ensured that connections do get closed.

In this commit, the behaviour is adjusted such that connections are
closed when last communication interface pointing to the server/serial
port (i.e. "endpoint") is close()'d.

With modbus binding this basically means when the tcp or serial thing
is removed / disabled, the connections is closed/freed, but only if it
is the last thing pointing to that server or serial port.

Since modbus.sunspec binding reuses modbus serial & tcp endpoint things,
the same note applies for modbus.sunspec binding.

This is change in functional behaviour but in a way is logical. We can
further introduce to have "delayed"/"deferred"/"debounce" connection
closing connections as per the setting reconnectAfterMillis even in
situations where communication interface is still open.

* [modbus] Check and disconnect idle connections without transactions
* [modbus] mvn spotless:apply
* [modbubs] Fixed log message
* [modbus] Race condition fix

The CountDownLatch was used as a guard (latch.await) in many tests to
wait for callbacks to be called before proceeding with assertions.

Since the latch was countDown() beginning of the callback, we introduced
a race condition with the subsequent assertions and updating the other
counters used in the subsequent assertions.

This commit updates the CountDownLatch as the last step of the callback,
resolving the race condition.

* [modbus] small test fix
* [modbus] readcallback changed to nonnull
* [modbus] Refactored ModbusCommunicationInterface to have seperate callback for result and failure

However I had to dig deep to reach all the affected parts.
Also there is a new callback, and a new "result" type to communicate
the failures.
* [modbus] SmokeTest refactored to new api
* [modbus] Modbus bundle refactored to use the new api
* [modbus][sunspec] refactored sunspec bundle to use the new modbus API
* [modbus] refactor modbus tests to use the new api
* [modbus] Removed ModbusWriteCallback interface from ModbusDataThingHandler
Also reset ModbusDataThingHandler testWriteHandling generic to it's original form
* [modbus] ModbusDataThingHandler does not implement ModbusReadCallback anymore
Instead it has a public onReadResult method. ModbusPollerThingHandler changed to
work with ModbusDataThingHandler children.
* [modbus] Fixed caching in ModbusPollerThingHandler
* [modbus] read modbus data as Optionals
* [modbus] toString for PollResult
* [modbus] fixed confusing variable name
* [modbus] Disallow null callbacks
* [modbus] mvn spotless:apply
* [modbus] Removing Nullable decorations
* [modbus] submitOneTimeWrite simplification
* [modbus] Less verbose logging
* [modbus] submitOneTimePoll simplification
* [modbus] Less verbose logging
* [modbus] Many null warnings removed
* [modbus] Fix: no need for a @nonnull annotation because it is default
* [modbus] Removing unneeded Nullable, using final in immutables
* [modbus] Explicit functional interface
* [modbus] @nullable and @NonNullByDefault aligned with coding conventions
* [modbus] Collections.emptyMap instead of allocating new map every time.

Signed-off-by: Sami Salonen <ssalonen@gmail.com>

Co-authored-by: Nagy Attila Gábor <mrbig@sneaker.hu>
Signed-off-by: MPH80 <michael@hazelden.me>
andrewfg pushed a commit to andrewfg/openhab-addons that referenced this pull request Aug 31, 2020
* [modbus] connection closing behaviour finetuned

The binding closes TCP/serial connections connections as per user
configuration. It is either
- every time, immediately after a transaction (reconnectAfterMillis=0)
- *after* a read/write transaction, if the connection has been open "too
  long" (configurable with reconnectAfterMillis)

We have an obvious downside to this simple logic -- the connection
can remain open "indefinitely" if there are no transactions occurring.
With Modbus we are quite often dealing with PLCs and other embedded
systems with limited resources, and "reserving" the connection is not
always something we want.

Previously (2.5.x branch) connections were also closed when
- a regular poll task was unregistered (i.e. we stop reading regularly
from a modbus server ("slave"). Since most users have regular polling in
place, so this ensured that connections do get closed.

In this commit, the behaviour is adjusted such that connections are
closed when last communication interface pointing to the server/serial
port (i.e. "endpoint") is close()'d.

With modbus binding this basically means when the tcp or serial thing
is removed / disabled, the connections is closed/freed, but only if it
is the last thing pointing to that server or serial port.

Since modbus.sunspec binding reuses modbus serial & tcp endpoint things,
the same note applies for modbus.sunspec binding.

This is change in functional behaviour but in a way is logical. We can
further introduce to have "delayed"/"deferred"/"debounce" connection
closing connections as per the setting reconnectAfterMillis even in
situations where communication interface is still open.

* [modbus] Check and disconnect idle connections without transactions
* [modbus] mvn spotless:apply
* [modbubs] Fixed log message
* [modbus] Race condition fix

The CountDownLatch was used as a guard (latch.await) in many tests to
wait for callbacks to be called before proceeding with assertions.

Since the latch was countDown() beginning of the callback, we introduced
a race condition with the subsequent assertions and updating the other
counters used in the subsequent assertions.

This commit updates the CountDownLatch as the last step of the callback,
resolving the race condition.

* [modbus] small test fix
* [modbus] readcallback changed to nonnull
* [modbus] Refactored ModbusCommunicationInterface to have seperate callback for result and failure

However I had to dig deep to reach all the affected parts.
Also there is a new callback, and a new "result" type to communicate
the failures.
* [modbus] SmokeTest refactored to new api
* [modbus] Modbus bundle refactored to use the new api
* [modbus][sunspec] refactored sunspec bundle to use the new modbus API
* [modbus] refactor modbus tests to use the new api
* [modbus] Removed ModbusWriteCallback interface from ModbusDataThingHandler
Also reset ModbusDataThingHandler testWriteHandling generic to it's original form
* [modbus] ModbusDataThingHandler does not implement ModbusReadCallback anymore
Instead it has a public onReadResult method. ModbusPollerThingHandler changed to
work with ModbusDataThingHandler children.
* [modbus] Fixed caching in ModbusPollerThingHandler
* [modbus] read modbus data as Optionals
* [modbus] toString for PollResult
* [modbus] fixed confusing variable name
* [modbus] Disallow null callbacks
* [modbus] mvn spotless:apply
* [modbus] Removing Nullable decorations
* [modbus] submitOneTimeWrite simplification
* [modbus] Less verbose logging
* [modbus] submitOneTimePoll simplification
* [modbus] Less verbose logging
* [modbus] Many null warnings removed
* [modbus] Fix: no need for a @nonnull annotation because it is default
* [modbus] Removing unneeded Nullable, using final in immutables
* [modbus] Explicit functional interface
* [modbus] @nullable and @NonNullByDefault aligned with coding conventions
* [modbus] Collections.emptyMap instead of allocating new map every time.

Signed-off-by: Sami Salonen <ssalonen@gmail.com>

Co-authored-by: Nagy Attila Gábor <mrbig@sneaker.hu>
andrewfg pushed a commit to andrewfg/openhab-addons that referenced this pull request Aug 31, 2020
* [modbus] connection closing behaviour finetuned

The binding closes TCP/serial connections connections as per user
configuration. It is either
- every time, immediately after a transaction (reconnectAfterMillis=0)
- *after* a read/write transaction, if the connection has been open "too
  long" (configurable with reconnectAfterMillis)

We have an obvious downside to this simple logic -- the connection
can remain open "indefinitely" if there are no transactions occurring.
With Modbus we are quite often dealing with PLCs and other embedded
systems with limited resources, and "reserving" the connection is not
always something we want.

Previously (2.5.x branch) connections were also closed when
- a regular poll task was unregistered (i.e. we stop reading regularly
from a modbus server ("slave"). Since most users have regular polling in
place, so this ensured that connections do get closed.

In this commit, the behaviour is adjusted such that connections are
closed when last communication interface pointing to the server/serial
port (i.e. "endpoint") is close()'d.

With modbus binding this basically means when the tcp or serial thing
is removed / disabled, the connections is closed/freed, but only if it
is the last thing pointing to that server or serial port.

Since modbus.sunspec binding reuses modbus serial & tcp endpoint things,
the same note applies for modbus.sunspec binding.

This is change in functional behaviour but in a way is logical. We can
further introduce to have "delayed"/"deferred"/"debounce" connection
closing connections as per the setting reconnectAfterMillis even in
situations where communication interface is still open.

* [modbus] Check and disconnect idle connections without transactions
* [modbus] mvn spotless:apply
* [modbubs] Fixed log message
* [modbus] Race condition fix

The CountDownLatch was used as a guard (latch.await) in many tests to
wait for callbacks to be called before proceeding with assertions.

Since the latch was countDown() beginning of the callback, we introduced
a race condition with the subsequent assertions and updating the other
counters used in the subsequent assertions.

This commit updates the CountDownLatch as the last step of the callback,
resolving the race condition.

* [modbus] small test fix
* [modbus] readcallback changed to nonnull
* [modbus] Refactored ModbusCommunicationInterface to have seperate callback for result and failure

However I had to dig deep to reach all the affected parts.
Also there is a new callback, and a new "result" type to communicate
the failures.
* [modbus] SmokeTest refactored to new api
* [modbus] Modbus bundle refactored to use the new api
* [modbus][sunspec] refactored sunspec bundle to use the new modbus API
* [modbus] refactor modbus tests to use the new api
* [modbus] Removed ModbusWriteCallback interface from ModbusDataThingHandler
Also reset ModbusDataThingHandler testWriteHandling generic to it's original form
* [modbus] ModbusDataThingHandler does not implement ModbusReadCallback anymore
Instead it has a public onReadResult method. ModbusPollerThingHandler changed to
work with ModbusDataThingHandler children.
* [modbus] Fixed caching in ModbusPollerThingHandler
* [modbus] read modbus data as Optionals
* [modbus] toString for PollResult
* [modbus] fixed confusing variable name
* [modbus] Disallow null callbacks
* [modbus] mvn spotless:apply
* [modbus] Removing Nullable decorations
* [modbus] submitOneTimeWrite simplification
* [modbus] Less verbose logging
* [modbus] submitOneTimePoll simplification
* [modbus] Less verbose logging
* [modbus] Many null warnings removed
* [modbus] Fix: no need for a @nonnull annotation because it is default
* [modbus] Removing unneeded Nullable, using final in immutables
* [modbus] Explicit functional interface
* [modbus] @nullable and @NonNullByDefault aligned with coding conventions
* [modbus] Collections.emptyMap instead of allocating new map every time.

Signed-off-by: Sami Salonen <ssalonen@gmail.com>

Co-authored-by: Nagy Attila Gábor <mrbig@sneaker.hu>
andrewfg pushed a commit to andrewfg/openhab-addons that referenced this pull request Aug 31, 2020
* [modbus] connection closing behaviour finetuned

The binding closes TCP/serial connections connections as per user
configuration. It is either
- every time, immediately after a transaction (reconnectAfterMillis=0)
- *after* a read/write transaction, if the connection has been open "too
  long" (configurable with reconnectAfterMillis)

We have an obvious downside to this simple logic -- the connection
can remain open "indefinitely" if there are no transactions occurring.
With Modbus we are quite often dealing with PLCs and other embedded
systems with limited resources, and "reserving" the connection is not
always something we want.

Previously (2.5.x branch) connections were also closed when
- a regular poll task was unregistered (i.e. we stop reading regularly
from a modbus server ("slave"). Since most users have regular polling in
place, so this ensured that connections do get closed.

In this commit, the behaviour is adjusted such that connections are
closed when last communication interface pointing to the server/serial
port (i.e. "endpoint") is close()'d.

With modbus binding this basically means when the tcp or serial thing
is removed / disabled, the connections is closed/freed, but only if it
is the last thing pointing to that server or serial port.

Since modbus.sunspec binding reuses modbus serial & tcp endpoint things,
the same note applies for modbus.sunspec binding.

This is change in functional behaviour but in a way is logical. We can
further introduce to have "delayed"/"deferred"/"debounce" connection
closing connections as per the setting reconnectAfterMillis even in
situations where communication interface is still open.

* [modbus] Check and disconnect idle connections without transactions
* [modbus] mvn spotless:apply
* [modbubs] Fixed log message
* [modbus] Race condition fix

The CountDownLatch was used as a guard (latch.await) in many tests to
wait for callbacks to be called before proceeding with assertions.

Since the latch was countDown() beginning of the callback, we introduced
a race condition with the subsequent assertions and updating the other
counters used in the subsequent assertions.

This commit updates the CountDownLatch as the last step of the callback,
resolving the race condition.

* [modbus] small test fix
* [modbus] readcallback changed to nonnull
* [modbus] Refactored ModbusCommunicationInterface to have seperate callback for result and failure

However I had to dig deep to reach all the affected parts.
Also there is a new callback, and a new "result" type to communicate
the failures.
* [modbus] SmokeTest refactored to new api
* [modbus] Modbus bundle refactored to use the new api
* [modbus][sunspec] refactored sunspec bundle to use the new modbus API
* [modbus] refactor modbus tests to use the new api
* [modbus] Removed ModbusWriteCallback interface from ModbusDataThingHandler
Also reset ModbusDataThingHandler testWriteHandling generic to it's original form
* [modbus] ModbusDataThingHandler does not implement ModbusReadCallback anymore
Instead it has a public onReadResult method. ModbusPollerThingHandler changed to
work with ModbusDataThingHandler children.
* [modbus] Fixed caching in ModbusPollerThingHandler
* [modbus] read modbus data as Optionals
* [modbus] toString for PollResult
* [modbus] fixed confusing variable name
* [modbus] Disallow null callbacks
* [modbus] mvn spotless:apply
* [modbus] Removing Nullable decorations
* [modbus] submitOneTimeWrite simplification
* [modbus] Less verbose logging
* [modbus] submitOneTimePoll simplification
* [modbus] Less verbose logging
* [modbus] Many null warnings removed
* [modbus] Fix: no need for a @nonnull annotation because it is default
* [modbus] Removing unneeded Nullable, using final in immutables
* [modbus] Explicit functional interface
* [modbus] @nullable and @NonNullByDefault aligned with coding conventions
* [modbus] Collections.emptyMap instead of allocating new map every time.

Signed-off-by: Sami Salonen <ssalonen@gmail.com>

Co-authored-by: Nagy Attila Gábor <mrbig@sneaker.hu>
andrewfg pushed a commit to andrewfg/openhab-addons that referenced this pull request Aug 31, 2020
* [modbus] connection closing behaviour finetuned

The binding closes TCP/serial connections connections as per user
configuration. It is either
- every time, immediately after a transaction (reconnectAfterMillis=0)
- *after* a read/write transaction, if the connection has been open "too
  long" (configurable with reconnectAfterMillis)

We have an obvious downside to this simple logic -- the connection
can remain open "indefinitely" if there are no transactions occurring.
With Modbus we are quite often dealing with PLCs and other embedded
systems with limited resources, and "reserving" the connection is not
always something we want.

Previously (2.5.x branch) connections were also closed when
- a regular poll task was unregistered (i.e. we stop reading regularly
from a modbus server ("slave"). Since most users have regular polling in
place, so this ensured that connections do get closed.

In this commit, the behaviour is adjusted such that connections are
closed when last communication interface pointing to the server/serial
port (i.e. "endpoint") is close()'d.

With modbus binding this basically means when the tcp or serial thing
is removed / disabled, the connections is closed/freed, but only if it
is the last thing pointing to that server or serial port.

Since modbus.sunspec binding reuses modbus serial & tcp endpoint things,
the same note applies for modbus.sunspec binding.

This is change in functional behaviour but in a way is logical. We can
further introduce to have "delayed"/"deferred"/"debounce" connection
closing connections as per the setting reconnectAfterMillis even in
situations where communication interface is still open.

* [modbus] Check and disconnect idle connections without transactions
* [modbus] mvn spotless:apply
* [modbubs] Fixed log message
* [modbus] Race condition fix

The CountDownLatch was used as a guard (latch.await) in many tests to
wait for callbacks to be called before proceeding with assertions.

Since the latch was countDown() beginning of the callback, we introduced
a race condition with the subsequent assertions and updating the other
counters used in the subsequent assertions.

This commit updates the CountDownLatch as the last step of the callback,
resolving the race condition.

* [modbus] small test fix
* [modbus] readcallback changed to nonnull
* [modbus] Refactored ModbusCommunicationInterface to have seperate callback for result and failure

However I had to dig deep to reach all the affected parts.
Also there is a new callback, and a new "result" type to communicate
the failures.
* [modbus] SmokeTest refactored to new api
* [modbus] Modbus bundle refactored to use the new api
* [modbus][sunspec] refactored sunspec bundle to use the new modbus API
* [modbus] refactor modbus tests to use the new api
* [modbus] Removed ModbusWriteCallback interface from ModbusDataThingHandler
Also reset ModbusDataThingHandler testWriteHandling generic to it's original form
* [modbus] ModbusDataThingHandler does not implement ModbusReadCallback anymore
Instead it has a public onReadResult method. ModbusPollerThingHandler changed to
work with ModbusDataThingHandler children.
* [modbus] Fixed caching in ModbusPollerThingHandler
* [modbus] read modbus data as Optionals
* [modbus] toString for PollResult
* [modbus] fixed confusing variable name
* [modbus] Disallow null callbacks
* [modbus] mvn spotless:apply
* [modbus] Removing Nullable decorations
* [modbus] submitOneTimeWrite simplification
* [modbus] Less verbose logging
* [modbus] submitOneTimePoll simplification
* [modbus] Less verbose logging
* [modbus] Many null warnings removed
* [modbus] Fix: no need for a @nonnull annotation because it is default
* [modbus] Removing unneeded Nullable, using final in immutables
* [modbus] Explicit functional interface
* [modbus] @nullable and @NonNullByDefault aligned with coding conventions
* [modbus] Collections.emptyMap instead of allocating new map every time.

Signed-off-by: Sami Salonen <ssalonen@gmail.com>

Co-authored-by: Nagy Attila Gábor <mrbig@sneaker.hu>
DaanMeijer pushed a commit to DaanMeijer/openhab-addons that referenced this pull request Sep 1, 2020
* [modbus] connection closing behaviour finetuned

The binding closes TCP/serial connections connections as per user
configuration. It is either
- every time, immediately after a transaction (reconnectAfterMillis=0)
- *after* a read/write transaction, if the connection has been open "too
  long" (configurable with reconnectAfterMillis)

We have an obvious downside to this simple logic -- the connection
can remain open "indefinitely" if there are no transactions occurring.
With Modbus we are quite often dealing with PLCs and other embedded
systems with limited resources, and "reserving" the connection is not
always something we want.

Previously (2.5.x branch) connections were also closed when
- a regular poll task was unregistered (i.e. we stop reading regularly
from a modbus server ("slave"). Since most users have regular polling in
place, so this ensured that connections do get closed.

In this commit, the behaviour is adjusted such that connections are
closed when last communication interface pointing to the server/serial
port (i.e. "endpoint") is close()'d.

With modbus binding this basically means when the tcp or serial thing
is removed / disabled, the connections is closed/freed, but only if it
is the last thing pointing to that server or serial port.

Since modbus.sunspec binding reuses modbus serial & tcp endpoint things,
the same note applies for modbus.sunspec binding.

This is change in functional behaviour but in a way is logical. We can
further introduce to have "delayed"/"deferred"/"debounce" connection
closing connections as per the setting reconnectAfterMillis even in
situations where communication interface is still open.

* [modbus] Check and disconnect idle connections without transactions
* [modbus] mvn spotless:apply
* [modbubs] Fixed log message
* [modbus] Race condition fix

The CountDownLatch was used as a guard (latch.await) in many tests to
wait for callbacks to be called before proceeding with assertions.

Since the latch was countDown() beginning of the callback, we introduced
a race condition with the subsequent assertions and updating the other
counters used in the subsequent assertions.

This commit updates the CountDownLatch as the last step of the callback,
resolving the race condition.

* [modbus] small test fix
* [modbus] readcallback changed to nonnull
* [modbus] Refactored ModbusCommunicationInterface to have seperate callback for result and failure

However I had to dig deep to reach all the affected parts.
Also there is a new callback, and a new "result" type to communicate
the failures.
* [modbus] SmokeTest refactored to new api
* [modbus] Modbus bundle refactored to use the new api
* [modbus][sunspec] refactored sunspec bundle to use the new modbus API
* [modbus] refactor modbus tests to use the new api
* [modbus] Removed ModbusWriteCallback interface from ModbusDataThingHandler
Also reset ModbusDataThingHandler testWriteHandling generic to it's original form
* [modbus] ModbusDataThingHandler does not implement ModbusReadCallback anymore
Instead it has a public onReadResult method. ModbusPollerThingHandler changed to
work with ModbusDataThingHandler children.
* [modbus] Fixed caching in ModbusPollerThingHandler
* [modbus] read modbus data as Optionals
* [modbus] toString for PollResult
* [modbus] fixed confusing variable name
* [modbus] Disallow null callbacks
* [modbus] mvn spotless:apply
* [modbus] Removing Nullable decorations
* [modbus] submitOneTimeWrite simplification
* [modbus] Less verbose logging
* [modbus] submitOneTimePoll simplification
* [modbus] Less verbose logging
* [modbus] Many null warnings removed
* [modbus] Fix: no need for a @nonnull annotation because it is default
* [modbus] Removing unneeded Nullable, using final in immutables
* [modbus] Explicit functional interface
* [modbus] @nullable and @NonNullByDefault aligned with coding conventions
* [modbus] Collections.emptyMap instead of allocating new map every time.

Signed-off-by: Sami Salonen <ssalonen@gmail.com>

Co-authored-by: Nagy Attila Gábor <mrbig@sneaker.hu>
Signed-off-by: Daan Meijer <daan@studioseptember.nl>
markus7017 pushed a commit to markus7017/openhab-addons that referenced this pull request Sep 19, 2020
* [modbus] connection closing behaviour finetuned

The binding closes TCP/serial connections connections as per user
configuration. It is either
- every time, immediately after a transaction (reconnectAfterMillis=0)
- *after* a read/write transaction, if the connection has been open "too
  long" (configurable with reconnectAfterMillis)

We have an obvious downside to this simple logic -- the connection
can remain open "indefinitely" if there are no transactions occurring.
With Modbus we are quite often dealing with PLCs and other embedded
systems with limited resources, and "reserving" the connection is not
always something we want.

Previously (2.5.x branch) connections were also closed when
- a regular poll task was unregistered (i.e. we stop reading regularly
from a modbus server ("slave"). Since most users have regular polling in
place, so this ensured that connections do get closed.

In this commit, the behaviour is adjusted such that connections are
closed when last communication interface pointing to the server/serial
port (i.e. "endpoint") is close()'d.

With modbus binding this basically means when the tcp or serial thing
is removed / disabled, the connections is closed/freed, but only if it
is the last thing pointing to that server or serial port.

Since modbus.sunspec binding reuses modbus serial & tcp endpoint things,
the same note applies for modbus.sunspec binding.

This is change in functional behaviour but in a way is logical. We can
further introduce to have "delayed"/"deferred"/"debounce" connection
closing connections as per the setting reconnectAfterMillis even in
situations where communication interface is still open.

* [modbus] Check and disconnect idle connections without transactions
* [modbus] mvn spotless:apply
* [modbubs] Fixed log message
* [modbus] Race condition fix

The CountDownLatch was used as a guard (latch.await) in many tests to
wait for callbacks to be called before proceeding with assertions.

Since the latch was countDown() beginning of the callback, we introduced
a race condition with the subsequent assertions and updating the other
counters used in the subsequent assertions.

This commit updates the CountDownLatch as the last step of the callback,
resolving the race condition.

* [modbus] small test fix
* [modbus] readcallback changed to nonnull
* [modbus] Refactored ModbusCommunicationInterface to have seperate callback for result and failure

However I had to dig deep to reach all the affected parts.
Also there is a new callback, and a new "result" type to communicate
the failures.
* [modbus] SmokeTest refactored to new api
* [modbus] Modbus bundle refactored to use the new api
* [modbus][sunspec] refactored sunspec bundle to use the new modbus API
* [modbus] refactor modbus tests to use the new api
* [modbus] Removed ModbusWriteCallback interface from ModbusDataThingHandler
Also reset ModbusDataThingHandler testWriteHandling generic to it's original form
* [modbus] ModbusDataThingHandler does not implement ModbusReadCallback anymore
Instead it has a public onReadResult method. ModbusPollerThingHandler changed to
work with ModbusDataThingHandler children.
* [modbus] Fixed caching in ModbusPollerThingHandler
* [modbus] read modbus data as Optionals
* [modbus] toString for PollResult
* [modbus] fixed confusing variable name
* [modbus] Disallow null callbacks
* [modbus] mvn spotless:apply
* [modbus] Removing Nullable decorations
* [modbus] submitOneTimeWrite simplification
* [modbus] Less verbose logging
* [modbus] submitOneTimePoll simplification
* [modbus] Less verbose logging
* [modbus] Many null warnings removed
* [modbus] Fix: no need for a @nonnull annotation because it is default
* [modbus] Removing unneeded Nullable, using final in immutables
* [modbus] Explicit functional interface
* [modbus] @nullable and @NonNullByDefault aligned with coding conventions
* [modbus] Collections.emptyMap instead of allocating new map every time.

Signed-off-by: Sami Salonen <ssalonen@gmail.com>

Co-authored-by: Nagy Attila Gábor <mrbig@sneaker.hu>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement An enhancement or new feature for an existing add-on
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants