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 2018/08/20 12:51:29 UTC

[incubator-plc4x] branch feature/ethernet-ip updated: Further work on the EtherNet/IP implementation.

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

cdutz pushed a commit to branch feature/ethernet-ip
in repository https://gitbox.apache.org/repos/asf/incubator-plc4x.git


The following commit(s) were added to refs/heads/feature/ethernet-ip by this push:
     new ffd8d81  Further work on the EtherNet/IP implementation.
ffd8d81 is described below

commit ffd8d81f15dc53544fe643b58a408e3aaf475d4e
Author: Christofer Dutz <ch...@c-ware.de>
AuthorDate: Mon Aug 20 14:51:25 2018 +0200

    Further work on the EtherNet/IP implementation.
---
 plc4j/protocols/ethernetip/pom.xml                 |  16 ++-
 .../connection/BaseEtherNetIpPlcConnection.java    |  17 +---
 .../java/ethernetip/model/EtherNetIpAddress.java   |  22 ++++-
 .../ethernetip/netty/Plc4XEtherNetIpProtocol.java  |   6 ++
 .../ethernetip/src/site/asciidoc/index.adoc        |  14 ++-
 .../src/site/resources/img/WAGO_enip_exporer.png   | Bin 0 -> 293168 bytes
 .../org/apache/plc4x/java/ethernetip/EnipTest.java | 109 +++++++++++++++++++++
 .../java/ethernetip/ManualPlc4XEtherNetIpTest.java |   6 +-
 .../src/test/resources/read-attribute.pcapng       | Bin 0 -> 664 bytes
 9 files changed, 164 insertions(+), 26 deletions(-)

diff --git a/plc4j/protocols/ethernetip/pom.xml b/plc4j/protocols/ethernetip/pom.xml
index bf33a6c..413c847 100644
--- a/plc4j/protocols/ethernetip/pom.xml
+++ b/plc4j/protocols/ethernetip/pom.xml
@@ -61,16 +61,28 @@
       <scope>test</scope>
     </dependency>
 
-    <!--dependency>
+    <dependency>
       <groupId>com.digitalpetri.enip</groupId>
       <artifactId>cip-core</artifactId>
       <version>${ethernetip-driver.version}</version>
-    </dependency-->
+    </dependency>
     <dependency>
       <groupId>com.digitalpetri.enip</groupId>
       <artifactId>enip-core</artifactId>
       <version>${ethernetip-driver.version}</version>
     </dependency>
+    <dependency>
+      <groupId>com.digitalpetri.enip</groupId>
+      <artifactId>enip-client</artifactId>
+      <version>${ethernetip-driver.version}</version>
+      <scope>test</scope>
+    </dependency>
+    <dependency>
+      <groupId>com.digitalpetri.enip</groupId>
+      <artifactId>cip-client</artifactId>
+      <version>${ethernetip-driver.version}</version>
+      <scope>test</scope>
+    </dependency>
 
     <dependency>
       <groupId>io.netty</groupId>
diff --git a/plc4j/protocols/ethernetip/src/main/java/org/apache/plc4x/java/ethernetip/connection/BaseEtherNetIpPlcConnection.java b/plc4j/protocols/ethernetip/src/main/java/org/apache/plc4x/java/ethernetip/connection/BaseEtherNetIpPlcConnection.java
index ce2870a..bc8deb6 100644
--- a/plc4j/protocols/ethernetip/src/main/java/org/apache/plc4x/java/ethernetip/connection/BaseEtherNetIpPlcConnection.java
+++ b/plc4j/protocols/ethernetip/src/main/java/org/apache/plc4x/java/ethernetip/connection/BaseEtherNetIpPlcConnection.java
@@ -26,6 +26,7 @@ import org.apache.plc4x.java.api.model.Address;
 import org.apache.plc4x.java.base.connection.AbstractPlcConnection;
 import org.apache.plc4x.java.base.connection.ChannelFactory;
 import org.apache.plc4x.java.base.messages.PlcRequestContainer;
+import org.apache.plc4x.java.ethernetip.model.EtherNetIpAddress;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -57,19 +58,9 @@ public abstract class BaseEtherNetIpPlcConnection extends AbstractPlcConnection
 
     @Override
     public Address parseAddress(String addressString) {
-        /*if (MaskWriteRegisterModbusAddress.ADDRESS_PATTERN.matcher(addressString).matches()) {
-            return MaskWriteRegisterModbusAddress.of(addressString);
-        } else if (ReadDiscreteInputsModbusAddress.ADDRESS_PATTERN.matcher(addressString).matches()) {
-            return ReadDiscreteInputsModbusAddress.of(addressString);
-        } else if (ReadHoldingRegistersModbusAddress.ADDRESS_PATTERN.matcher(addressString).matches()) {
-            return ReadHoldingRegistersModbusAddress.of(addressString);
-        } else if (ReadInputRegistersModbusAddress.ADDRESS_PATTERN.matcher(addressString).matches()) {
-            return ReadInputRegistersModbusAddress.of(addressString);
-        } else if (CoilAddress.ADDRESS_PATTERN.matcher(addressString).matches()) {
-            return CoilAddress.of(addressString);
-        } else if (RegisterAddress.ADDRESS_PATTERN.matcher(addressString).matches()) {
-            return RegisterAddress.of(addressString);
-        }*/
+        if(EtherNetIpAddress.ADDRESS_PATTERN.matcher(addressString).matches()) {
+            return EtherNetIpAddress.of(addressString);
+        }
         return null;
     }
 
diff --git a/plc4j/protocols/ethernetip/src/main/java/org/apache/plc4x/java/ethernetip/model/EtherNetIpAddress.java b/plc4j/protocols/ethernetip/src/main/java/org/apache/plc4x/java/ethernetip/model/EtherNetIpAddress.java
index 85ec148..56e818c 100644
--- a/plc4j/protocols/ethernetip/src/main/java/org/apache/plc4x/java/ethernetip/model/EtherNetIpAddress.java
+++ b/plc4j/protocols/ethernetip/src/main/java/org/apache/plc4x/java/ethernetip/model/EtherNetIpAddress.java
@@ -18,14 +18,16 @@ under the License.
 */
 package org.apache.plc4x.java.ethernetip.model;
 
+import org.apache.plc4x.java.api.exceptions.PlcRuntimeException;
 import org.apache.plc4x.java.api.model.Address;
 
 import java.util.Objects;
+import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-public abstract class EtherNetIpAddress implements Address {
+public class EtherNetIpAddress implements Address {
 
-    public static final Pattern ADDRESS_PATTERN = Pattern.compile("(?<address>\\d+)");
+    public static final Pattern ADDRESS_PATTERN = Pattern.compile("^#(?<class>.*?)#(?<instance>\\d{1,4})(?:#(?<attribute>\\d))?");
 
     private final int objectNumber;
     private final int instanceNumber;
@@ -33,6 +35,18 @@ public abstract class EtherNetIpAddress implements Address {
 
     private int connectionId;
 
+    public static EtherNetIpAddress of(String addressString) {
+        Matcher matcher = ADDRESS_PATTERN.matcher(addressString);
+        if (!matcher.matches()) {
+            throw new PlcRuntimeException(addressString + " doesn't match " + ADDRESS_PATTERN);
+        }
+        int classNumber = Integer.parseInt(matcher.group("class"));
+        int instanceNumber = Integer.parseInt(matcher.group("instance"));
+        int attributeNumber = Integer.parseInt(matcher.group("attribute"));
+
+        return new EtherNetIpAddress(classNumber, instanceNumber, attributeNumber);
+    }
+
     public EtherNetIpAddress(int objectNumber, int instanceNumber, int attributeNumber) {
         this.objectNumber = objectNumber;
         this.instanceNumber = instanceNumber;
@@ -77,8 +91,8 @@ public abstract class EtherNetIpAddress implements Address {
     public String toString() {
         return "EtherNetIpAddress{" +
             "object-number=" + objectNumber +
-            "instance-number=" + instanceNumber +
-            "attribute-number=" + attributeNumber +
+            ", instance-number=" + instanceNumber +
+            ", attribute-number=" + attributeNumber +
             '}';
     }
 }
diff --git a/plc4j/protocols/ethernetip/src/main/java/org/apache/plc4x/java/ethernetip/netty/Plc4XEtherNetIpProtocol.java b/plc4j/protocols/ethernetip/src/main/java/org/apache/plc4x/java/ethernetip/netty/Plc4XEtherNetIpProtocol.java
index 2e53530..f4e9fd7 100644
--- a/plc4j/protocols/ethernetip/src/main/java/org/apache/plc4x/java/ethernetip/netty/Plc4XEtherNetIpProtocol.java
+++ b/plc4j/protocols/ethernetip/src/main/java/org/apache/plc4x/java/ethernetip/netty/Plc4XEtherNetIpProtocol.java
@@ -167,6 +167,12 @@ public class Plc4XEtherNetIpProtocol extends MessageToMessageCodec<EnipPacket, P
         }
 
         PlcReadRequest request = (PlcReadRequest) msg.getRequest();
+
+/*        CpfItem cpfItem = new ConnectedDataItemRequest();
+        CpfPacket cpfPacket = new CpfPacket(cpfItem);
+        EnipPacket enipPacket = new EnipPacket(CommandCode.SendRRData, 0, EnipStatus.EIP_SUCCESS,
+            messageId.getAndIncrement(), new SendRRData(cpfPacket));
+*/
     }
 
     ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
diff --git a/plc4j/protocols/ethernetip/src/site/asciidoc/index.adoc b/plc4j/protocols/ethernetip/src/site/asciidoc/index.adoc
index eb169d5..32dee12 100644
--- a/plc4j/protocols/ethernetip/src/site/asciidoc/index.adoc
+++ b/plc4j/protocols/ethernetip/src/site/asciidoc/index.adoc
@@ -20,11 +20,13 @@
 `IP` in `EtherNet/IP` does not relate to the `Internet Protocol` but to `Industrial Protocol`, so EtherNet/IP does not necessarily have to go over `IP` but can be implemented on `ethernet frames`.
 This driver however will utilize `EtherNet/IP` based on `TCP` and `UDP`.
 
-`Explicit messaging`: Read explicitly addressed information (Pull) (CIP Class 3 and 2)
-`Implicit messaging`: Read information distributed by the device configuration (Push) (CIP Class 1 and 0)
+EIP (the short form of: EtherNet/IP) generally supports two types of communication:
 
-TCP used for Explicit Messaging
-UDP used for Implicit Messaging ("Real time" I/O)
+- `Explicit messaging`: Read explicitly addressed information (Pull) (CIP Class 3 and 2)
+- `Implicit messaging`: Read information distributed by the device configuration (Push) (CIP Class 1 and 0)
+
+TCP transport is used for Explicit Messaging
+UDP transport is used for Implicit Messaging ("Real time" I/O)
 
 In Explicit Messaging (classic Request/Response) there are two different types of access:
 
@@ -37,10 +39,13 @@ In all cases the payload of the data items is dependent on the encapsulated prot
 In our case we'll concentrate on the default CIP format.
 
 Explicitly addressed object consists of the following data:
+
 - Object-Number
 - Instance-Number
 - Attribute-Number
 
+The format we have decided to use is: `#{objectId}#{instanceId}#{attributeId}`.
+
 Here is the location of the Spec:
 https://www.odva.org/Portals/0/Library/Publications_Numbered/PUB00123R1_Common-Industrial_Protocol_and_Family_of_CIP_Networks.pdf
 
@@ -49,6 +54,7 @@ As EtherNet/IP is used to encapsulate CIP traffic, this spec contains a chapter
 Another documentation I found discussing the explicit messaging thing a little better is this:
 http://www.deltamotion.com/support/webhelp/rmctools/Communications/Ethernet/Supported_Protocols/EtherNetIP/EtherNet_IP_Explicit_Messaging.htm
 https://www.odva.org/Portals/0/Library/Publications_Numbered/PUB00213R0_EtherNetIP_Developers_Guide.pdf
+http://www.technologyuk.net/telecommunications/industrial-networks/cip.shtml
 
 === Reading
 
diff --git a/plc4j/protocols/ethernetip/src/site/resources/img/WAGO_enip_exporer.png b/plc4j/protocols/ethernetip/src/site/resources/img/WAGO_enip_exporer.png
new file mode 100644
index 0000000..01c3446
Binary files /dev/null and b/plc4j/protocols/ethernetip/src/site/resources/img/WAGO_enip_exporer.png differ
diff --git a/plc4j/protocols/ethernetip/src/test/java/org/apache/plc4x/java/ethernetip/EnipTest.java b/plc4j/protocols/ethernetip/src/test/java/org/apache/plc4x/java/ethernetip/EnipTest.java
new file mode 100644
index 0000000..9cd6c69
--- /dev/null
+++ b/plc4j/protocols/ethernetip/src/test/java/org/apache/plc4x/java/ethernetip/EnipTest.java
@@ -0,0 +1,109 @@
+package org.apache.plc4x.java.ethernetip;
+
+import com.digitalpetri.enip.EtherNetIpClient;
+import com.digitalpetri.enip.EtherNetIpClientConfig;
+import com.digitalpetri.enip.cip.CipClient;
+import com.digitalpetri.enip.cip.epath.EPath;
+import com.digitalpetri.enip.cip.epath.LogicalSegment;
+import com.digitalpetri.enip.cip.epath.PortSegment;
+import com.digitalpetri.enip.cip.services.GetAttributeListService;
+import com.digitalpetri.enip.cip.services.GetAttributeSingleService;
+import com.digitalpetri.enip.commands.ListIdentity;
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.buffer.UnpooledDirectByteBuf;
+import io.netty.util.ReferenceCountUtil;
+
+import java.time.Duration;
+import java.util.Arrays;
+
+/*
+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
+
+  http://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.
+*/
+public class EnipTest {
+
+    public static void main(String[] args) throws Exception {
+        EtherNetIpClientConfig config = EtherNetIpClientConfig.builder("10.10.64.30")
+            .setSerialNumber(0x00)
+            .setVendorId(0x00)
+            .setTimeout(Duration.ofSeconds(2))
+            .build();
+
+        // backplane, slot 0
+        EPath.PaddedEPath connectionPath = new EPath.PaddedEPath(
+            new PortSegment(1, new byte[]{(byte) 0}));
+
+        CipClient client = new CipClient(config, connectionPath);
+
+        client.connect().get();
+
+        GetAttributeSingleService service = new GetAttributeSingleService(
+            new EPath.PaddedEPath(new LogicalSegment.ClassId(0x04), new LogicalSegment.InstanceId(0x69), new LogicalSegment.AttributeId(0x03)));
+
+        ////////////////////////////////////////////////////////////////
+        // Doesn't work:
+        client.invokeUnconnected(service).whenComplete((as, ex) -> {
+            if (as != null) {
+                try {
+                    /*ByteBuf data = as[0].getData();
+                    int major = data.readUnsignedByte();
+                    int minor = data.readUnsignedByte();
+
+                    System.out.println(String.format("firmware v%s.%s", major, minor));*/
+                } catch (Throwable t) {
+                    t.printStackTrace();
+                } finally {
+                    //Arrays.stream(as).forEach(a -> ReferenceCountUtil.release(a.getData()));
+                }
+            } else {
+                ex.printStackTrace();
+            }
+        });
+
+        ////////////////////////////////////////////////////////////////
+        // Works:
+        ByteBuf buf = Unpooled.buffer();
+        service.encodeRequest(buf);
+        client.sendUnconnectedData(buf).whenComplete((as, ex) -> {
+            if (as != null) {
+                try {
+                    byte serviceId = as.readByte();
+                    boolean response = (serviceId & 128) != 0;
+                    serviceId = (byte) (serviceId & (byte) 127);
+                    if((serviceId != 0x0E) || !response) {
+                        System.out.println("Error");
+                    }
+                    // Reserved
+                    as.readByte();
+                    byte status = as.readByte();
+                    byte statusSize = as.readByte();
+                    short value = as.readShort();
+
+                    System.out.println(String.format("Value is %s", value));
+                } catch (Throwable t) {
+                    t.printStackTrace();
+                } finally {
+
+                }
+            } else {
+                ex.printStackTrace();
+            }
+        });
+    }
+
+}
diff --git a/plc4j/protocols/ethernetip/src/test/java/org/apache/plc4x/java/ethernetip/ManualPlc4XEtherNetIpTest.java b/plc4j/protocols/ethernetip/src/test/java/org/apache/plc4x/java/ethernetip/ManualPlc4XEtherNetIpTest.java
index f5b332c..73c4be8 100644
--- a/plc4j/protocols/ethernetip/src/test/java/org/apache/plc4x/java/ethernetip/ManualPlc4XEtherNetIpTest.java
+++ b/plc4j/protocols/ethernetip/src/test/java/org/apache/plc4x/java/ethernetip/ManualPlc4XEtherNetIpTest.java
@@ -33,14 +33,14 @@ public class ManualPlc4XEtherNetIpTest {
     public static void main(String... args) {
         String connectionUrl;
         System.out.println("Using tcp");
-        connectionUrl = "eip://192.168.42.39:44818";
-        //connectionUrl = "eip://10.10.64.30:44818";
+        //connectionUrl = "eip://192.168.42.39:44818";
+        connectionUrl = "eip://10.10.64.30:44818";
         try (PlcConnection plcConnection = new PlcDriverManager().getConnection(connectionUrl)) {
             System.out.println("PlcConnection " + plcConnection);
 
             PlcReader reader = plcConnection.getReader().orElseThrow(() -> new RuntimeException("No Reader found"));
 
-            Address address = plcConnection.parseAddress("register:7");
+            Address address = plcConnection.parseAddress("#1#1#1");
             CompletableFuture<TypeSafePlcReadResponse<Integer>> response = reader
                 .read(new TypeSafePlcReadRequest<>(Integer.class, address));
             TypeSafePlcReadResponse<Integer> readResponse = response.get();
diff --git a/plc4j/protocols/ethernetip/src/test/resources/read-attribute.pcapng b/plc4j/protocols/ethernetip/src/test/resources/read-attribute.pcapng
new file mode 100644
index 0000000..6e40071
Binary files /dev/null and b/plc4j/protocols/ethernetip/src/test/resources/read-attribute.pcapng differ