You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@plc4x.apache.org by cd...@apache.org on 2023/06/07 07:32:51 UTC

[plc4x] branch chore/profinet-phase-3 updated (f8587652f1 -> 8896bcbff6)

This is an automated email from the ASF dual-hosted git repository.

cdutz pushed a change to branch chore/profinet-phase-3
in repository https://gitbox.apache.org/repos/asf/plc4x.git


    from f8587652f1 chore(plc4j/profinet): Refactored the code to be a little more cleaned up.
     new 7628e1f4d8 docs(plc4go/bacnet): Added some comments
     new 8896bcbff6 refactor(plc4j/udp-transport): Made it generally possible to open a UDP transport with a fixed local port

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 plc4go/internal/bacnetip/Driver.go                 |   2 +
 .../java/spi/connection/NettyChannelFactory.java   |  20 ++-
 .../java/transport/udp/UdpChannelFactory.java      |   8 +-
 .../plc4x/java/transport/udp/UdpTransport.java     |  15 +-
 .../transport/udp/UdpTransportConfiguration.java   |   9 ++
 src/site/asciidoc/developers/architecture.adoc     | 162 +++++++++++++++++++++
 6 files changed, 202 insertions(+), 14 deletions(-)
 create mode 100644 src/site/asciidoc/developers/architecture.adoc


[plc4x] 01/02: docs(plc4go/bacnet): Added some comments

Posted by cd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

cdutz pushed a commit to branch chore/profinet-phase-3
in repository https://gitbox.apache.org/repos/asf/plc4x.git

commit 7628e1f4d8ec2f746f02734728bde784ce6e1832
Author: Christofer Dutz <cd...@apache.org>
AuthorDate: Wed Jun 7 09:10:26 2023 +0200

    docs(plc4go/bacnet): Added some comments
---
 plc4go/internal/bacnetip/Driver.go             |   2 +
 src/site/asciidoc/developers/architecture.adoc | 162 +++++++++++++++++++++++++
 2 files changed, 164 insertions(+)

diff --git a/plc4go/internal/bacnetip/Driver.go b/plc4go/internal/bacnetip/Driver.go
index d5d2a57f2f..a14b40324b 100644
--- a/plc4go/internal/bacnetip/Driver.go
+++ b/plc4go/internal/bacnetip/Driver.go
@@ -123,6 +123,7 @@ type ApplicationManager struct {
 func (a *ApplicationManager) getApplicationLayerMessageCodec(transport *udp.Transport, transportUrl url.URL, options map[string][]string) (*ApplicationLayerMessageCodec, error) {
 	var localAddress *net.UDPAddr
 	var remoteAddr *net.UDPAddr
+	// Find out the remote and the local ip address by opening an UPD port (which is instantly closed)
 	{
 		host := transportUrl.Host
 		port := transportUrl.Port()
@@ -134,6 +135,7 @@ func (a *ApplicationManager) getApplicationLayerMessageCodec(transport *udp.Tran
 		} else {
 			remoteAddr = resolvedRemoteAddr
 		}
+		// TODO: Possibly do with with ip-address matching similar to the raw-socket impl in Java.
 		if dial, err := net.DialUDP("udp", nil, remoteAddr); err != nil {
 			return nil, errors.Errorf("couldn't dial to host %#v", transportUrl.Host)
 		} else {
diff --git a/src/site/asciidoc/developers/architecture.adoc b/src/site/asciidoc/developers/architecture.adoc
new file mode 100644
index 0000000000..0e95ebf201
--- /dev/null
+++ b/src/site/asciidoc/developers/architecture.adoc
@@ -0,0 +1,162 @@
+//
+//  Licensed to the Apache Software Foundation (ASF) under one or more
+//  contributor license agreements.  See the NOTICE file distributed with
+//  this work for additional information regarding copyright ownership.
+//  The ASF licenses this file to You under the Apache License, Version 2.0
+//  (the "License"); you may not use this file except in compliance with
+//  the License.  You may obtain a copy of the License at
+//
+//      https://www.apache.org/licenses/LICENSE-2.0
+//
+//  Unless required by applicable law or agreed to in writing, software
+//  distributed under the License is distributed on an "AS IS" BASIS,
+//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//  See the License for the specific language governing permissions and
+//  limitations under the License.
+//
+
+== Architecture of PLC4X Connections
+
+=== Simple case
+
+In this simple case, an application asks the DriverManager to forward a connection creation to the corresponding Driver implementation, which then creates both a Connection and a MessageCodec instance. The Connection is the logical link between the connection state and the MessageCodec.
+A MessageCodec uses a TransportInstance to communicate with the target device.
+
+[plantuml, target=no-restrictions-simple, format=png]
+....
+
+package Connection1 {
+Plc4xConnection1 : s7:tcp://192.168.23.30
+TcpPort1 : local-ip          192.168.23.200
+TcpPort1 : local-port      48479
+TcpPort1 : remote-ip     192.168.23.30
+TcpPort1 : remote-port 102
+}
+
+package Connection2 {
+Plc4xConnection2 : s7:tcp://192.168.23.30
+TcpPort2 : local-ip          192.168.23.200
+TcpPort2 : local-port      34555
+TcpPort2 : remote-ip     192.168.23.30
+TcpPort2 : remote-port 102
+}
+
+S7Device : local-ip     192.168.23.30
+S7Device : local-port 102
+
+Plc4xConnection1 -> TcpPort1
+Plc4xConnection2 -> TcpPort2
+TcpPort1 -> S7Device : tcp
+TcpPort2 -> S7Device : tcp
+
+....
+
+[plantuml, target=no-restrictions, format=png]
+....
+
+package "Application" {
+  [APP]
+}
+
+package "PLC4X" {
+    [Connection]
+    [ReaderWriter]
+    [MessageCodec]
+    [Transport]
+    [TransportInstance]
+    [Driver]
+}
+
+package "PLC" {
+    [Device]
+}
+
+APP -> Connection : n:1
+Connection - MessageCodec : 1:1
+Connection -- Driver : n:1
+Connection -- ReaderWriter : 1:1
+MessageCodec -- TransportInstance : 1:1
+Transport - TransportInstance : 1:1
+MessageCodec - Driver : n:1
+TransportInstance -> Device : 1:1
+
+note top of [Connection] : logical
+
+....
+
+=== Problems
+
+Serial transports based on RS475 and UDP Transports currently don't allow sharing. That means only one connection instance can have access to one RS485 or one shared local UDP Port (Multiple UDP transport instances with different local ports however are possible). As soon as one connection is established and a second connection would try to access this, this would result in errors.
+
+However, multiple devices could be attached to the same RS458 port (Modbus RTU and Modbus ASCII explicitly supports this, however using different devices using different protocols over the same port is not possible) and in BACnet connecting to multiple remote BACnet devices would require one local UDP port to be used by multiple connections.
+
+=== Protocols requiring us to use a fixed port on a non-broadcast address
+
+Some protocols, such as BACnet require remotes to send data to a fixed udp port on a non-broadcast address. This causes problems as soon as we want to connect to multiple BACnet devices from the same host as only one instance can get access to that port.
+
+[plantuml, target=shared-local-port-simple, format=png]
+....
+
+package Connection1 {
+Plc4xConnection1 : bacnet:udp://192.168.23.30
+UdpPort1 : local-ip          192.168.23.200
+UdpPort1 : local-port      47808
+UdpPort1 : remote-ip     192.168.23.30
+UdpPort1 : remote-port 47808
+}
+
+package Connection2 #red {
+Plc4xConnection2 : bacnet:udp://192.168.23.20
+UdpPort2 : local-ip          192.168.23.200
+UdpPort2 : local-port      47808
+UdpPort2 : remote-ip     192.168.23.20
+UdpPort2 : remote-port 47808
+}
+note right of UdpPort2: This local port is already owned by Connection1
+
+BACnetDevice : local-ip     192.168.23.30
+BACnetDevice : local-port 102
+
+Plc4xConnection1 -> UdpPort1
+Plc4xConnection2 -> UdpPort2
+UdpPort1 -> BACnetDevice : udp
+UdpPort2 -> BACnetDevice : udp
+
+....
+
+[plantuml, target=shared-local-port, format=png]
+....
+
+package "Application" {
+  [APP]
+}
+
+package "PLC4X" {
+    [Connection]
+    [ReaderWriter]
+    [MessageCodec]
+    [Transport]
+    [TransportInstance]
+    [Driver]
+}
+
+package "PLC" {
+    [Device]
+}
+
+APP -> Connection : n:1
+Connection - MessageCodec : n:1
+Connection -- Driver : n:1
+Connection -- ReaderWriter : 1:1
+MessageCodec -- TransportInstance : 1:1
+Transport - TransportInstance : 1:1
+MessageCodec - Driver : n:1
+TransportInstance -> Device : 1:n
+
+note top of [Connection] : logical
+
+....
+
+=== Protocols only allowing one connection at a time
+
+


[plc4x] 02/02: refactor(plc4j/udp-transport): Made it generally possible to open a UDP transport with a fixed local port

Posted by cd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

cdutz pushed a commit to branch chore/profinet-phase-3
in repository https://gitbox.apache.org/repos/asf/plc4x.git

commit 8896bcbff664094a09854cedb351ff09b671eacb
Author: Christofer Dutz <cd...@apache.org>
AuthorDate: Wed Jun 7 09:32:41 2023 +0200

    refactor(plc4j/udp-transport): Made it generally possible to open a UDP transport with a fixed local port
---
 .../java/spi/connection/NettyChannelFactory.java     | 20 ++++++++++++--------
 .../plc4x/java/transport/udp/UdpChannelFactory.java  |  8 ++++++--
 .../plc4x/java/transport/udp/UdpTransport.java       | 15 +++++++++++----
 .../transport/udp/UdpTransportConfiguration.java     |  9 +++++++++
 4 files changed, 38 insertions(+), 14 deletions(-)

diff --git a/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/connection/NettyChannelFactory.java b/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/connection/NettyChannelFactory.java
index 2901877bef..5652882eec 100644
--- a/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/connection/NettyChannelFactory.java
+++ b/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/connection/NettyChannelFactory.java
@@ -44,13 +44,16 @@ public abstract class NettyChannelFactory implements ChannelFactory {
 
     private final Map<Channel, EventLoopGroup> eventLoops = new ConcurrentHashMap<>();
 
-    /**
-     * TODO should be removed together with the Constructor.
-     */
-    private final SocketAddress address;
+    private final SocketAddress localAddress;
+    private final SocketAddress remoteAddress;
+
+    protected NettyChannelFactory(SocketAddress remoteAddress) {
+        this(null, remoteAddress);
+    }
 
-    protected NettyChannelFactory(SocketAddress address) {
-        this.address = address;
+    protected NettyChannelFactory(SocketAddress localAddress, SocketAddress remoteAddress) {
+        this.localAddress = localAddress;
+        this.remoteAddress = remoteAddress;
     }
 
     /**
@@ -83,7 +86,7 @@ public abstract class NettyChannelFactory implements ChannelFactory {
      * Has to be in accordance with {@link #getChannel()}
      * otherwise a Runtime Exception will be produced by Netty
      * <p>
-     * By Default Nettys {@link NioEventLoopGroup} is used.
+     * By Default Netty's {@link NioEventLoopGroup} is used.
      * Transports which have to use a different EventLoopGroup have to override {#getEventLoopGroup()}.
      */
     public EventLoopGroup getEventLoopGroup() {
@@ -106,7 +109,8 @@ public abstract class NettyChannelFactory implements ChannelFactory {
             configureBootstrap(bootstrap);
             bootstrap.handler(channelHandler);
             // Start the client.
-            final ChannelFuture f = bootstrap.connect(address);
+            final ChannelFuture f = (localAddress == null) ?
+                bootstrap.connect(remoteAddress) : bootstrap.connect(remoteAddress, localAddress);
             f.addListener(future -> {
                 if (!future.isSuccess()) {
                     logger.info("Unable to connect, shutting down worker thread.");
diff --git a/plc4j/transports/udp/src/main/java/org/apache/plc4x/java/transport/udp/UdpChannelFactory.java b/plc4j/transports/udp/src/main/java/org/apache/plc4x/java/transport/udp/UdpChannelFactory.java
index d13053edff..7fc9fe2d1a 100644
--- a/plc4j/transports/udp/src/main/java/org/apache/plc4x/java/transport/udp/UdpChannelFactory.java
+++ b/plc4j/transports/udp/src/main/java/org/apache/plc4x/java/transport/udp/UdpChannelFactory.java
@@ -36,8 +36,12 @@ public class UdpChannelFactory extends NettyChannelFactory implements HasConfigu
 
     private UdpTransportConfiguration configuration;
 
-    public UdpChannelFactory(SocketAddress address) {
-        super(address);
+    public UdpChannelFactory(SocketAddress remoteAddress) {
+        super(remoteAddress);
+    }
+
+    public UdpChannelFactory(SocketAddress localAddress, SocketAddress remoteAddress) {
+        super(localAddress, remoteAddress);
     }
 
     @Override
diff --git a/plc4j/transports/udp/src/main/java/org/apache/plc4x/java/transport/udp/UdpTransport.java b/plc4j/transports/udp/src/main/java/org/apache/plc4x/java/transport/udp/UdpTransport.java
index 8579c70f76..2d90a352de 100644
--- a/plc4j/transports/udp/src/main/java/org/apache/plc4x/java/transport/udp/UdpTransport.java
+++ b/plc4j/transports/udp/src/main/java/org/apache/plc4x/java/transport/udp/UdpTransport.java
@@ -62,20 +62,27 @@ public class UdpTransport implements Transport, HasConfiguration<UdpTransportCon
 
         // If the port wasn't specified, try to get a default port from the configuration.
         int port;
+        int localPort = UdpTransportConfiguration.NO_DEFAULT_PORT;
         if(portString != null) {
             port = Integer.parseInt(portString);
-        } else if ((configuration != null) &&
-            (configuration.getDefaultPort() != UdpTransportConfiguration.NO_DEFAULT_PORT)) {
+        } else if ((configuration != null) &&  (configuration.getDefaultPort() != UdpTransportConfiguration.NO_DEFAULT_PORT)) {
             port = configuration.getDefaultPort();
         } else {
             throw new PlcRuntimeException("No port defined");
         }
+        if (configuration != null) {
+            localPort = configuration.getLocalPort();
+        }
 
         // Create the fully qualified remote socket address which we should connect to.
-        SocketAddress address = new InetSocketAddress((ip == null) ? hostname : ip, port);
+        SocketAddress remoteAddress = new InetSocketAddress((ip == null) ? hostname : ip, port);
+        if(localPort != UdpTransportConfiguration.NO_DEFAULT_PORT) {
+            SocketAddress localAddress = new InetSocketAddress(localPort);
+            return new UdpChannelFactory(localAddress, remoteAddress);
+        }
 
         // Initialize the channel factory with the default socket address we want to connect to.
-        return new UdpChannelFactory(address);
+        return new UdpChannelFactory(remoteAddress);
     }
 
 }
diff --git a/plc4j/transports/udp/src/main/java/org/apache/plc4x/java/transport/udp/UdpTransportConfiguration.java b/plc4j/transports/udp/src/main/java/org/apache/plc4x/java/transport/udp/UdpTransportConfiguration.java
index 2c26fdf2f8..8d35b04fdf 100644
--- a/plc4j/transports/udp/src/main/java/org/apache/plc4x/java/transport/udp/UdpTransportConfiguration.java
+++ b/plc4j/transports/udp/src/main/java/org/apache/plc4x/java/transport/udp/UdpTransportConfiguration.java
@@ -28,4 +28,13 @@ public interface UdpTransportConfiguration extends TransportConfiguration {
         return NO_DEFAULT_PORT;
     }
 
+    /**
+     * Most transports don't care about the local port, but some do.
+     * This option allows forcing a local port number to be used.
+     * @return local port number
+     */
+    default int getLocalPort() {
+        return NO_DEFAULT_PORT;
+    }
+
 }