Description
I wrote a Camel route that reads signals from a Phoenix I/O via modbus protocol every 500 milliseconds.
The I/o Phoenix is connected to my laptop via ethernet cable.
Initially the connection was opened and closed for each read instruction, but subsequently I moved to a managed pool of connections to reuse the same connection.
In this way I managed to reduce reading times and avoided saturating the PLC connections.
Before, opening and closing the connection, took me a total of 1 (max 2) seconds to read each reading.
With a managed pool of already open connections, I can read within the expected 500 ms.
The problem I'm facing now is that if the ethernet cable is disconnected, an Exception is thrown that I can't catch and subsequently the connection to that IP is no longer usable until the route is restarted.
Do you have any idea on how to catch the java.net.SocketException Connection reset?
This is the java.net.SocketException thrown:
2024-09-23T18:18:44.111+02:00 WARN 29312 --- [ntLoopGroup-3-1] i.netty.channel.DefaultChannelPipeline : An exceptionCaught() event was fired, and it reached at the tail of the pipeline. It usually means the last handler in the pipeline did not handle the exception. java.net.SocketException: Connection reset at java.base/sun.nio.ch.SocketChannelImpl.throwConnectionReset(SocketChannelImpl.java:394) ~[na:na] at java.base/sun.nio.ch.SocketChannelImpl.read(SocketChannelImpl.java:426) ~[na:na] at io.netty.buffer.PooledByteBuf.setBytes(PooledByteBuf.java:254) ~[netty-buffer-4.1.97.Final.jar:4.1.97.Final] at io.netty.buffer.AbstractByteBuf.writeBytes(AbstractByteBuf.java:1132) ~[netty-buffer-4.1.97.Final.jar:4.1.97.Final] at io.netty.channel.socket.nio.NioSocketChannel.doReadBytes(NioSocketChannel.java:357) ~[netty-transport-4.1.97.Final.jar:4.1.97.Final] at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:151) ~[netty-transport-4.1.97.Final.jar:4.1.97.Final] at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:788) ~[netty-transport-4.1.97.Final.jar:4.1.97.Final] at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:724) ~[netty-transport-4.1.97.Final.jar:4.1.97.Final] at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:650) ~[netty-transport-4.1.97.Final.jar:4.1.97.Final] at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:562) ~[netty-transport-4.1.97.Final.jar:4.1.97.Final] at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:997) ~[netty-common-4.1.97.Final.jar:4.1.97.Final] at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74) ~[netty-common-4.1.97.Final.jar:4.1.97.Final] at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) ~[netty-common-4.1.97.Final.jar:4.1.97.Final] at java.base/java.lang.Thread.run(Thread.java:840) ~[na:na]
This is the Camel route:
fromF("timer:%s?period=%d", tagGroupName, interval)
.routeId(tagGroupName)
.process(exchange -> {
Map<String, Object> result = tagReaderService.directRead(tagGroupTriggerData.getHost(),
tagGroupTriggerData.getPort(),
DevicePlugin.MODBUS,
tagGroupTriggerData.getTagList());
exchange.getIn().setBody(result);
})
.marshal().json(JsonLibrary.Jackson)
.process(exchange -> {
String body = exchange.getIn().getBody(String.class);
tagReaderService.put(tagGroupName, body);
});
This the tagReaderService.directRead method:
public Map<String, Object> directRead(final String host, final Integer port, final DevicePlugin devicePlugin,
final List<TagTriggerData> tags) throws PlcConnectionException, ExecutionException, InterruptedException {
if (devicePlugin == DevicePlugin.OMRON_FINS_UDP) {
Map<String, Object> result = new HashMap<>(tags.size());
for (var tagDTO : tags) {
OmronReadRequest request = new OmronReadRequest();
request.setAddress(tagDTO.getAddress());
OmronReadResponse response = producerTemplate.requestBody("direct:read-omron-single", request, OmronReadResponse.class);
result.put(tagDTO.getName(), response.getResponse());
}
return result;
} else {
return plcPoolConnectionService.readValues(host, port, devicePlugin, tags);
}
}
This is the plcPoolConnectionService.readValues invoked:
public Map<String, Object> readValues(final String host, final Integer port, final DevicePlugin devicePlugin, final List<TagTriggerData> tags) throws PlcConnectionException, ExecutionException, InterruptedException {
ConnectionWrapper connection = getConnectionWrapper(host, port, devicePlugin);
return connection.readValues(tags);
}
This is the getConnectionWrapper:
private ConnectionWrapper getConnectionWrapper(final String host, final Integer port, final DevicePlugin devicePlugin) throws PlcConnectionException {
String key = host + ofNullable(port).map(Object::toString).orElse("");
ConnectionWrapper connection = connections.get(key);
if (connection == null) {
connection = createNewConnection(host, port, devicePlugin);
connections.put(key, connection);
}
return connection;
}
And here i create the connection:
private ConnectionWrapper createNewConnection(final String host, final Integer port, final DevicePlugin devicePlugin) throws PlcConnectionException {
return switch (devicePlugin) {
case S7COMM -> ConnectionWrapper.s7Connection(driverManager, host);
case MODBUS -> ConnectionWrapper.modbusTcpConnection(driverManager, host, port);
default -> throw new RuntimeException("Unsupported device plugin: " + devicePlugin);
};
}
public static ConnectionWrapper modbusTcpConnection(final PlcDriverManager driverManager, final String host, final Integer port) throws PlcConnectionException {
String connectionString = String.format(MODBUS_TCP_CONNECTION_STRING, host);
return new ConnectionWrapper(driverManager, connectionString);
}
private ConnectionWrapper(final PlcDriverManager driverManager, final String connectionString) throws PlcConnectionException {
connection = driverManager.getConnection(connectionString);
}
Feel free to write to me for any information and if there are any points to explore further.
Thank you,
Luca