You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@plc4x.apache.org by hu...@apache.org on 2022/05/25 09:41:57 UTC

[plc4x] 02/02: Outline of Logix Driver

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

hutcheb pushed a commit to branch logix_develop
in repository https://gitbox.apache.org/repos/asf/plc4x.git

commit 2c70689c5e6e96a0b55b486801ed7adfbfbc9099
Author: Ben Hutcheson <be...@gmail.com>
AuthorDate: Wed May 25 19:41:25 2022 +1000

    Outline of Logix Driver
---
 plc4j/drivers/logix/pom.xml                        | 170 ++++++
 .../plc4x/java/logix/readwrite/LogixDriver.java    | 126 +++++
 .../configuration/LogixConfiguration.java          |  53 ++
 .../java/logix/readwrite/field/LogixField.java     | 158 ++++++
 .../logix/readwrite/field/LogixFieldHandler.java   |  35 ++
 .../readwrite/protocol/LogixProtocolLogic.java     | 590 +++++++++++++++++++++
 .../services/org.apache.plc4x.java.api.PlcDriver   |  19 +
 .../java/eip/readwrite/LogixDriverTestsuite.java   |  29 +
 .../eip/readwrite/LogixParserSerializerTest.java   |  29 +
 plc4j/drivers/logix/src/test/resources/logback.xml |  36 ++
 plc4j/drivers/pom.xml                              |   1 +
 .../src/main/resources/protocols/logix/logix.mspec |   6 +-
 12 files changed, 1249 insertions(+), 3 deletions(-)

diff --git a/plc4j/drivers/logix/pom.xml b/plc4j/drivers/logix/pom.xml
new file mode 100644
index 0000000000..08d85200a1
--- /dev/null
+++ b/plc4j/drivers/logix/pom.xml
@@ -0,0 +1,170 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  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.
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.plc4x</groupId>
+    <artifactId>plc4j-drivers</artifactId>
+    <version>0.10.0-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>plc4j-driver-logix</artifactId>
+  <name>PLC4J: Driver: Allen Bradley Logix</name>
+  <description>Implementation of a PLC4X driver able to speak using a custom Ethernet/IP Protocol in line with Allen Bradley Logix controllers.
+  </description>
+
+  <build>
+    <plugins>
+      <plugin>
+        <groupId>org.apache.plc4x.plugins</groupId>
+        <artifactId>plc4x-maven-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>test</id>
+            <phase>generate-sources</phase>
+            <goals>
+              <goal>generate-driver</goal>
+            </goals>
+            <configuration>
+              <protocolName>logix</protocolName>
+              <languageName>java</languageName>
+              <outputFlavor>read-write</outputFlavor>
+            </configuration>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.karaf.tooling</groupId>
+        <artifactId>karaf-maven-plugin</artifactId>
+        <executions>
+          <execution>
+            <id>generate-feature-xml</id>
+            <phase>compile</phase>
+            <goals>
+              <!-- Generate the feature.xml -->
+              <goal>features-generate-descriptor</goal>
+              <!-- Check the feature.xml -->
+              <goal>verify</goal>
+            </goals>
+            <configuration>
+              <enableGeneration>true</enableGeneration>
+              <aggregateFeatures>true</aggregateFeatures>
+            </configuration>
+          </execution>
+          <execution>
+            <id>build-kar</id>
+            <phase>package</phase>
+            <goals>
+              <!--
+                Build a kar archive (Jar containing the feature.xml
+                as well as the module content and it's dependencies.
+              -->
+              <goal>kar</goal>
+            </goals>
+          </execution>
+        </executions>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <extensions>true</extensions>
+        <configuration>
+          <instructions>
+            <Bundle-SymbolicName>${project.groupId}.${project.artifactId}</Bundle-SymbolicName>
+            <Bundle-Activator>org.apache.plc4x.java.osgi.DriverActivator</Bundle-Activator>
+            <Export-Service>org.apache.plc4x.java.api.PlcDriver,org.apache.plc4x.java.logix.readwrite.LogixDriver
+            </Export-Service>
+            <Import-Package>
+              com.fasterxml.jackson.annotation;resolution:=optional,
+              *
+            </Import-Package>
+          </instructions>
+        </configuration>
+      </plugin>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-dependency-plugin</artifactId>
+        <configuration>
+          <usedDependencies combine.children="append">
+            <usedDependency>org.apache.plc4x:plc4x-code-generation-language-java</usedDependency>
+            <usedDependency>org.apache.plc4x:plc4x-protocols-logix</usedDependency>
+          </usedDependencies>
+        </configuration>
+      </plugin>
+    </plugins>
+  </build>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.apache.plc4x</groupId>
+      <artifactId>plc4j-api</artifactId>
+      <version>0.10.0-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.plc4x</groupId>
+      <artifactId>plc4j-spi</artifactId>
+      <version>0.10.0-SNAPSHOT</version>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.plc4x</groupId>
+      <artifactId>plc4j-transport-tcp</artifactId>
+      <version>0.10.0-SNAPSHOT</version>
+    </dependency>
+
+    <dependency>
+      <groupId>io.netty</groupId>
+      <artifactId>netty-buffer</artifactId>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.plc4x</groupId>
+      <artifactId>plc4j-utils-test-utils</artifactId>
+      <version>0.10.0-SNAPSHOT</version>
+      <scope>test</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.plc4x</groupId>
+      <artifactId>plc4x-code-generation-language-java</artifactId>
+      <version>0.10.0-SNAPSHOT</version>
+      <!-- Scope is 'provided' as this way it's not shipped with the driver -->
+      <scope>provided</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>org.apache.plc4x</groupId>
+      <artifactId>plc4x-protocols-logix</artifactId>
+      <version>0.10.0-SNAPSHOT</version>
+      <!-- Scope is 'provided' as this way it's not shipped with the driver -->
+      <scope>provided</scope>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.plc4x</groupId>
+      <artifactId>plc4x-protocols-logix</artifactId>
+      <version>0.10.0-SNAPSHOT</version>
+      <classifier>tests</classifier>
+      <type>test-jar</type>
+      <scope>test</scope>
+    </dependency>
+  </dependencies>
+
+</project>
diff --git a/plc4j/drivers/logix/src/main/java/org/apache/plc4x/java/logix/readwrite/LogixDriver.java b/plc4j/drivers/logix/src/main/java/org/apache/plc4x/java/logix/readwrite/LogixDriver.java
new file mode 100644
index 0000000000..47c28f45e0
--- /dev/null
+++ b/plc4j/drivers/logix/src/main/java/org/apache/plc4x/java/logix/readwrite/LogixDriver.java
@@ -0,0 +1,126 @@
+/*
+ * 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.
+ */
+package org.apache.plc4x.java.logix.readwrite;
+
+import io.netty.buffer.ByteBuf;
+import org.apache.plc4x.java.logix.readwrite.configuration.LogixConfiguration;
+import org.apache.plc4x.java.logix.readwrite.field.LogixField;
+import org.apache.plc4x.java.logix.readwrite.field.LogixFieldHandler;
+import org.apache.plc4x.java.logix.readwrite.protocol.LogixProtocolLogic;
+import org.apache.plc4x.java.spi.values.IEC61131ValueHandler;
+import org.apache.plc4x.java.api.value.PlcValueHandler;
+import org.apache.plc4x.java.spi.configuration.Configuration;
+import org.apache.plc4x.java.spi.connection.GeneratedDriverBase;
+import org.apache.plc4x.java.spi.connection.PlcFieldHandler;
+import org.apache.plc4x.java.spi.connection.ProtocolStackConfigurer;
+import org.apache.plc4x.java.spi.connection.SingleProtocolStackConfigurer;
+
+import java.util.function.Consumer;
+import java.util.function.ToIntFunction;
+
+public class LogixDriver extends GeneratedDriverBase<EipPacket> {
+    public static final int PORT = 44818;
+    @Override
+    public String getProtocolCode() {
+        return "eip";
+    }
+
+    @Override
+    public String getProtocolName() {
+        return "EthernetIP";
+    }
+
+    @Override
+    protected Class<? extends Configuration> getConfigurationType() {
+        return LogixConfiguration.class;
+    }
+
+    @Override
+    protected PlcFieldHandler getFieldHandler() {
+        return new LogixFieldHandler();
+    }
+
+    @Override
+    protected PlcValueHandler getValueHandler() {
+        return new IEC61131ValueHandler();
+    }
+
+    /**
+     * This protocol doesn't have a disconnect procedure, so there is no need to wait for a login to finish.
+     * @return false
+     */
+    @Override
+    protected boolean awaitDisconnectComplete() {
+        return false;
+    }
+
+    @Override
+    protected String getDefaultTransport() {
+        return "tcp";
+    }
+
+    @Override
+    protected boolean canRead() {
+        return true;
+    }
+
+    @Override
+    protected boolean canWrite() {
+        return true;
+    }
+
+    @Override
+    protected ProtocolStackConfigurer<EipPacket> getStackConfigurer() {
+        return SingleProtocolStackConfigurer.builder(EipPacket.class, EipPacket::staticParse)
+            .withProtocol(LogixProtocolLogix.class)
+            .withPacketSizeEstimator(ByteLengthEstimator.class)
+            .littleEndian()
+            .build();
+    }
+
+    /** Estimate the Length of a Packet */
+    public static class ByteLengthEstimator implements ToIntFunction<ByteBuf> {
+        @Override
+        public int applyAsInt(ByteBuf byteBuf) {
+            if (byteBuf.readableBytes() >= 4) {
+                //Second byte for the size and then add the header size 24
+                int size = byteBuf.getUnsignedShort(byteBuf.readerIndex()+1)+24;
+                return size;
+            }
+            return -1;
+        }
+    }
+
+     /**Consumes all Bytes till another Magic Byte is found */
+    public static class CorruptPackageCleaner implements Consumer<ByteBuf> {
+        @Override
+        public void accept(ByteBuf byteBuf) {
+            while (byteBuf.getUnsignedByte(0) != 0x00) {
+                // Just consume the bytes till the next possible start position.
+                byteBuf.readByte();
+            }
+        }
+    }
+
+    @Override
+    public LogixField prepareField(String query){
+        return LogixField.of(query);
+    }
+
+}
diff --git a/plc4j/drivers/logix/src/main/java/org/apache/plc4x/java/logix/readwrite/configuration/LogixConfiguration.java b/plc4j/drivers/logix/src/main/java/org/apache/plc4x/java/logix/readwrite/configuration/LogixConfiguration.java
new file mode 100644
index 0000000000..9402357d61
--- /dev/null
+++ b/plc4j/drivers/logix/src/main/java/org/apache/plc4x/java/logix/readwrite/configuration/LogixConfiguration.java
@@ -0,0 +1,53 @@
+/*
+ * 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.
+ */
+package org.apache.plc4x.java.logix.readwrite.configuration;
+
+import org.apache.plc4x.java.logix.readwrite.LogixDriver;
+import org.apache.plc4x.java.spi.configuration.Configuration;
+import org.apache.plc4x.java.spi.configuration.annotations.ConfigurationParameter;
+import org.apache.plc4x.java.transport.tcp.TcpTransportConfiguration;
+
+public class LogixConfiguration implements Configuration, TcpTransportConfiguration {
+
+    @ConfigurationParameter
+    private int backplane;
+
+    @ConfigurationParameter
+    private int slot;
+
+    public int getBackplane() {
+        return backplane;
+    }
+
+    public void setBackplane(int backpane) {
+        this.backplane = backpane;
+    }
+
+    public int getSlot() {
+        return slot;
+    }
+
+    public void setSlot(int slot) {
+        this.slot = slot;
+    }
+
+    @Override
+    public int getDefaultPort(){return LogixDriver.PORT;}
+
+}
diff --git a/plc4j/drivers/logix/src/main/java/org/apache/plc4x/java/logix/readwrite/field/LogixField.java b/plc4j/drivers/logix/src/main/java/org/apache/plc4x/java/logix/readwrite/field/LogixField.java
new file mode 100644
index 0000000000..39d9d194b5
--- /dev/null
+++ b/plc4j/drivers/logix/src/main/java/org/apache/plc4x/java/logix/readwrite/field/LogixField.java
@@ -0,0 +1,158 @@
+/*
+ * 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.
+ */
+package org.apache.plc4x.java.logix.readwrite.field;
+
+import org.apache.plc4x.java.api.model.PlcField;
+import org.apache.plc4x.java.logix.readwrite.CIPDataTypeCode;
+import org.apache.plc4x.java.spi.generation.SerializationException;
+import org.apache.plc4x.java.spi.generation.WriteBuffer;
+import org.apache.plc4x.java.spi.utils.Serializable;
+
+import java.nio.charset.StandardCharsets;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class LogixField implements PlcField, Serializable {
+
+    private static final Pattern ADDRESS_PATTERN =
+        Pattern.compile("^%(?<tag>[a-zA-Z_.0-9]+\\[?[0-9]*\\]?):?(?<dataType>[A-Z]*):?(?<elementNb>[0-9]*)");
+
+    private static final String TAG = "tag";
+    private static final String ELEMENTS = "elementNb";
+    private static final String TYPE = "dataType";
+
+
+    private final String tag;
+    private CIPDataTypeCode type;
+    private int elementNb;
+
+    public CIPDataTypeCode getType() {
+        return type;
+    }
+
+    public void setType(CIPDataTypeCode type) {
+        this.type = type;
+    }
+
+    public int getElementNb() {
+        return elementNb;
+    }
+
+    public void setElementNb(int elementNb) {
+        this.elementNb = elementNb;
+    }
+
+    public String getTag() {
+        return tag;
+    }
+
+    public LogixField(String tag) {
+        this.tag = tag;
+    }
+
+    public LogixField(String tag, int elementNb) {
+        this.tag = tag;
+        this.elementNb = elementNb;
+    }
+
+    public LogixField(String tag, CIPDataTypeCode type, int elementNb) {
+        this.tag = tag;
+        this.type = type;
+        this.elementNb = elementNb;
+    }
+
+    public LogixField(String tag, CIPDataTypeCode type) {
+        this.tag = tag;
+        this.type = type;
+    }
+
+    public static boolean matches(String fieldQuery) {
+        return ADDRESS_PATTERN.matcher(fieldQuery).matches();
+    }
+
+    public static LogixField of(String fieldString) {
+        Matcher matcher = ADDRESS_PATTERN.matcher(fieldString);
+        if (matcher.matches()) {
+            String tag = matcher.group(TAG);
+            int nb = 0;
+            CIPDataTypeCode type = null;
+            if (!matcher.group(ELEMENTS).isEmpty()) {
+                nb = Integer.parseInt(matcher.group(ELEMENTS));
+            }
+            if (!matcher.group(TYPE).isEmpty()) {
+                type = CIPDataTypeCode.valueOf(matcher.group(TYPE));
+            }
+            if (nb != 0) {
+                if (type != null) {
+                    return new LogixField(tag, type, nb);
+                }
+                return new LogixField(tag, nb);
+            } else {
+                if (type != null) {
+                    return new LogixField(tag, type);
+                }
+                return new LogixField(tag);
+            }
+        }
+        return null;
+    }
+
+    @Override
+    public String getPlcDataType() {
+        return type.toString();
+    }
+
+    @Override
+    public Class<?> getDefaultJavaType() {
+        switch (type) {
+            //ToDo differenciate Short, Integer and Long
+            case INT:
+            case DINT:
+            case SINT:
+            case LINT:
+                return java.lang.Integer.class;
+            case STRING:
+            case STRING36:
+                return java.lang.String.class;
+            case REAL:
+                return java.lang.Double.class;
+            case BOOL:
+                return java.lang.Boolean.class;
+            default:
+                return Object.class;
+        }
+    }
+
+    @Override
+    public void serialize(WriteBuffer writeBuffer) throws SerializationException {
+        writeBuffer.pushContext(getClass().getSimpleName());
+
+        writeBuffer.writeString("node", tag.getBytes(StandardCharsets.UTF_8).length * 8, StandardCharsets.UTF_8.name(), tag);
+        if (type != null) {
+            writeBuffer.writeString("type", type.name().getBytes(StandardCharsets.UTF_8).length * 8, StandardCharsets.UTF_8.name(), type.name());
+        }
+        writeBuffer.writeUnsignedInt("elementNb", 16, elementNb);
+        // TODO: remove this (not language agnostic)
+        String defaultJavaType = (type == null ? Object.class : getDefaultJavaType()).getName();
+        writeBuffer.writeString("defaultJavaType", defaultJavaType.getBytes(StandardCharsets.UTF_8).length * 8, StandardCharsets.UTF_8.name(), defaultJavaType);
+
+        writeBuffer.popContext(getClass().getSimpleName());
+    }
+
+}
diff --git a/plc4j/drivers/logix/src/main/java/org/apache/plc4x/java/logix/readwrite/field/LogixFieldHandler.java b/plc4j/drivers/logix/src/main/java/org/apache/plc4x/java/logix/readwrite/field/LogixFieldHandler.java
new file mode 100644
index 0000000000..bfd97dc53b
--- /dev/null
+++ b/plc4j/drivers/logix/src/main/java/org/apache/plc4x/java/logix/readwrite/field/LogixFieldHandler.java
@@ -0,0 +1,35 @@
+/*
+ * 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.
+ */
+package org.apache.plc4x.java.logix.readwrite.field;
+
+import org.apache.plc4x.java.api.exceptions.PlcInvalidFieldException;
+import org.apache.plc4x.java.api.model.PlcField;
+import org.apache.plc4x.java.spi.connection.PlcFieldHandler;
+
+public class LogixFieldHandler implements PlcFieldHandler {
+
+    @Override
+    public PlcField createField(String fieldQuery) throws PlcInvalidFieldException {
+       if(EipField.matches(fieldQuery)){
+           return EipField.of(fieldQuery);
+       }
+       else throw new PlcInvalidFieldException("Invalid field "+fieldQuery);
+    }
+
+}
diff --git a/plc4j/drivers/logix/src/main/java/org/apache/plc4x/java/logix/readwrite/protocol/LogixProtocolLogic.java b/plc4j/drivers/logix/src/main/java/org/apache/plc4x/java/logix/readwrite/protocol/LogixProtocolLogic.java
new file mode 100644
index 0000000000..84a9dfcf8a
--- /dev/null
+++ b/plc4j/drivers/logix/src/main/java/org/apache/plc4x/java/logix/readwrite/protocol/LogixProtocolLogic.java
@@ -0,0 +1,590 @@
+/*
+ * 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.
+ */
+package org.apache.plc4x.java.logix.readwrite.protocol;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import org.apache.plc4x.java.api.exceptions.PlcRuntimeException;
+import org.apache.plc4x.java.api.messages.*;
+import org.apache.plc4x.java.api.model.PlcField;
+import org.apache.plc4x.java.api.types.PlcResponseCode;
+import org.apache.plc4x.java.api.value.*;
+import org.apache.plc4x.java.logix.readwrite.*;
+import org.apache.plc4x.java.logix.readwrite.configuration.LogixConfiguration;
+import org.apache.plc4x.java.logix.readwrite.field.LogixField;
+import org.apache.plc4x.java.spi.ConversationContext;
+import org.apache.plc4x.java.spi.Plc4xProtocolBase;
+import org.apache.plc4x.java.spi.configuration.HasConfiguration;
+import org.apache.plc4x.java.spi.generation.ParseException;
+import org.apache.plc4x.java.spi.generation.ReadBuffer;
+import org.apache.plc4x.java.spi.generation.ReadBufferByteBased;
+import org.apache.plc4x.java.spi.messages.*;
+import org.apache.plc4x.java.spi.messages.utils.ResponseItem;
+import org.apache.plc4x.java.spi.transaction.RequestTransactionManager;
+import org.apache.plc4x.java.spi.values.*;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
+import java.time.Duration;
+import java.util.*;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class LogixProtocolLogic extends Plc4xProtocolBase<EipPacket> implements HasConfiguration<LogixConfiguration> {
+
+    private static final Logger logger = LoggerFactory.getLogger(LogixProtocolLogic.class);
+    public static final Duration REQUEST_TIMEOUT = Duration.ofMillis(10000);
+
+    private static final List<Short> emptySenderContext = Arrays.asList((short) 0x00, (short) 0x00, (short) 0x00,
+        (short) 0x00, (short) 0x00, (short) 0x00, (short) 0x00, (short) 0x00);
+    private List<Short> senderContext;
+    private LogixConfiguration configuration;
+
+    private final AtomicInteger transactionCounterGenerator = new AtomicInteger(10);
+    private RequestTransactionManager tm;
+    private long sessionHandle;
+
+    @Override
+    public void setConfiguration(LogixConfiguration configuration) {
+        this.configuration = configuration;
+        // Set the transaction manager to allow only one message at a time.
+        this.tm = new RequestTransactionManager(1);
+    }
+
+    @Override
+    public void onConnect(ConversationContext<EipPacket> context) {
+        logger.debug("Sending RegisterSession EIP Package");
+        EipConnectionRequest connectionRequest =
+            new EipConnectionRequest(0L, 0L, emptySenderContext, 0L);
+        context.sendRequest(connectionRequest)
+            .expectResponse(EipPacket.class, REQUEST_TIMEOUT).unwrap(p -> p)
+            .check(p -> p instanceof EipConnectionRequest)
+            .handle(p -> {
+                if (p.getStatus() == 0L) {
+                    sessionHandle = p.getSessionHandle();
+                    senderContext = p.getSenderContext();
+                    logger.debug("Got assigned with Session {}", sessionHandle);
+                    // Send an event that connection setup is complete.
+                    context.fireConnected();
+                } else {
+                    logger.warn("Got status code [{}]", p.getStatus());
+                }
+
+            });
+    }
+
+    @Override
+    public CompletableFuture<PlcReadResponse> read(PlcReadRequest readRequest) {
+        DefaultPlcReadRequest request = (DefaultPlcReadRequest) readRequest;
+        List<CipReadRequest> requests = new ArrayList<>(request.getNumberOfFields());
+        for (PlcField field : request.getFields()) {
+            LogixField plcField = (LogixField) field;
+            String tag = plcField.getTag();
+            int elements = 1;
+            if (plcField.getElementNb() > 1) {
+                elements = plcField.getElementNb();
+            }
+            CipReadRequest req = new CipReadRequest(getRequestSize(tag), toAnsi(tag), elements, -1);
+            requests.add(req);
+        }
+        return toPlcReadResponse(readRequest, readInternal(requests));
+    }
+
+    private byte getRequestSize(String tag) {
+        //We need the size of the request in words (0x91, tagLength, ... tag + possible pad)
+        // Taking half to get word size
+        boolean isArray = false;
+        boolean isStruct = false;
+        String tagIsolated = tag;
+        if (tag.contains("[")) {
+            isArray = true;
+            tagIsolated = tag.substring(0, tag.indexOf("["));
+        }
+
+        if (tag.contains(".")) {
+            isStruct = true;
+            tagIsolated = tagIsolated.replace(".", "");
+        }
+        int dataLength = (tagIsolated.length() + 2)
+            + (tagIsolated.length() % 2)
+            + (isArray ? 2 : 0)
+            + (isStruct ? 2 : 0);
+        byte requestPathSize = (byte) (dataLength / 2);
+        return requestPathSize;
+    }
+
+    private byte[] toAnsi(String tag) {
+        int arrayIndex = 0;
+        boolean isArray = false;
+        boolean isStruct = false;
+        String tagFinal = tag;
+        if (tag.contains("[")) {
+            isArray = true;
+            String index = tag.substring(tag.indexOf("[") + 1, tag.indexOf("]"));
+            arrayIndex = Integer.parseInt(index);
+            tagFinal = tag.substring(0, tag.indexOf("["));
+        }
+        if (tag.contains(".")) {
+            tagFinal = tag.substring(0, tag.indexOf("."));
+            isStruct = true;
+        }
+        boolean isPadded = tagFinal.length() % 2 != 0;
+        int dataSegLength = 2 + tagFinal.length()
+            + (isPadded ? 1 : 0)
+            + (isArray ? 2 : 0);
+
+        if (isStruct) {
+            for (String subStr : tag.substring(tag.indexOf(".") + 1).split("\\.", -1)) {
+                dataSegLength += 2 + subStr.length() + subStr.length() % 2;
+            }
+        }
+
+        ByteBuffer buffer = ByteBuffer.allocate(dataSegLength).order(ByteOrder.LITTLE_ENDIAN);
+
+        buffer.put((byte) 0x91);
+        buffer.put((byte) tagFinal.length());
+        byte[] tagBytes = null;
+        tagBytes = tagFinal.getBytes(StandardCharsets.US_ASCII);
+
+        buffer.put(tagBytes);
+        buffer.position(2 + tagBytes.length);
+
+
+        if (isPadded) {
+            buffer.put((byte) 0x00);
+        }
+
+        if (isArray) {
+            buffer.put((byte) 0x28);
+            buffer.put((byte) arrayIndex);
+        }
+        if (isStruct) {
+            buffer.put(toAnsi(tag.substring(tag.indexOf(".") + 1, tag.length())));
+        }
+        return buffer.array();
+    }
+
+    private CompletableFuture<PlcReadResponse> toPlcReadResponse(PlcReadRequest readRequest, CompletableFuture<CipService> response) {
+        return response
+            .thenApply(p -> {
+                return ((PlcReadResponse) decodeReadResponse(p, readRequest));
+            });
+    }
+
+    private CompletableFuture<CipService> readInternal(List<CipReadRequest> request) {
+        CompletableFuture<CipService> future = new CompletableFuture<>();
+        RequestTransactionManager.RequestTransaction transaction = tm.startRequest();
+        if (request.size() > 1) {
+
+            short nb = (short) request.size();
+            List<Integer> offsets = new ArrayList<>(nb);
+            int offset = 2 + nb * 2;
+            for (int i = 0; i < nb; i++) {
+                offsets.add(offset);
+                offset += request.get(i).getLengthInBytes();
+            }
+
+            List<CipService> serviceArr = new ArrayList<>(nb);
+            for (int i = 0; i < nb; i++) {
+                serviceArr.add(request.get(i));
+            }
+            Services data = new Services(nb, offsets, serviceArr, -1);
+            //Encapsulate the data
+
+            AnsiExtendedSymbolSegment pathSegment0 = new AnsiExtendedSymbolSegment();
+
+            CipRRData pkt = new CipRRData(sessionHandle, 0L, emptySenderContext, 0L, 0, 2,
+                new CipExchange(
+                    new CipUnconnectedRequest(
+                        pathSegment0.length(),
+                        pathSegment0
+                    ),
+                    -1
+                ),
+                -1
+            );
+
+
+            transaction.submit(() -> context.sendRequest(pkt)
+                .expectResponse(EipPacket.class, REQUEST_TIMEOUT)
+                .onTimeout(future::completeExceptionally)
+                .onError((p, e) -> future.completeExceptionally(e))
+                .check(p -> p instanceof CipRRData)
+                .check(p -> p.getSessionHandle() == sessionHandle)
+                //.check(p -> p.getSenderContext() == senderContext)
+                .unwrap(p -> (CipRRData) p)
+                .unwrap(p -> p.getExchange().getService()).check(p -> p instanceof MultipleServiceResponse)
+                .unwrap(p -> (MultipleServiceResponse) p)
+                .check(p -> p.getServiceNb() == nb)
+                .handle(p -> {
+                    future.complete(p);
+                    // Finish the request-transaction.
+                    transaction.endRequest();
+                }));
+        } else if (request.size() == 1) {
+            CipExchange exchange = new CipExchange(
+                new CipUnconnectedRequest(
+                    request.get(0), (byte) configuration.getBackplane(), (byte) configuration.getSlot(), -1
+                ),
+                -1
+            );
+            CipRRData pkt = new CipRRData(sessionHandle, 0L, emptySenderContext, 0L, exchange, -1);
+            transaction.submit(() -> context.sendRequest(pkt)
+                .expectResponse(EipPacket.class, REQUEST_TIMEOUT)
+                .onTimeout(future::completeExceptionally)
+                .onError((p, e) -> future.completeExceptionally(e))
+                .check(p -> p instanceof CipRRData)
+                .check(p -> p.getSessionHandle() == sessionHandle)
+                //.check(p -> p.getSenderContext() == senderContext)
+                .unwrap(p -> (CipRRData) p)
+                .unwrap(p -> p.getExchange().getService()).check(p -> p instanceof CipReadResponse)
+                .unwrap(p -> (CipReadResponse) p)
+                .handle(p -> {
+                    future.complete(p);
+                    // Finish the request-transaction.
+                    transaction.endRequest();
+                }));
+        }
+        return future;
+    }
+
+    private PlcResponse decodeReadResponse(CipService p, PlcReadRequest readRequest) {
+        Map<String, ResponseItem<PlcValue>> values = new HashMap<>();
+        // only 1 field
+        if (p instanceof CipReadResponse) {
+            CipReadResponse resp = (CipReadResponse) p;
+            String fieldName = readRequest.getFieldNames().iterator().next();
+            LogixField field = (LogixField) readRequest.getField(fieldName);
+            PlcResponseCode code = decodeResponseCode(resp.getStatus());
+            PlcValue plcValue = null;
+            CIPDataTypeCode type = resp.getDataType();
+            ByteBuf data = Unpooled.wrappedBuffer(resp.getData());
+            if (code == PlcResponseCode.OK) {
+                plcValue = parsePlcValue(field, data, type);
+            }
+            ResponseItem<PlcValue> result = new ResponseItem<>(code, plcValue);
+            values.put(fieldName, result);
+        }
+        //Multiple response
+        else if (p instanceof MultipleServiceResponse) {
+            MultipleServiceResponse responses = (MultipleServiceResponse) p;
+            int nb = responses.getServiceNb();
+            List<CipService> arr = new ArrayList<>(nb);
+            ReadBufferByteBased read = new ReadBufferByteBased(responses.getServicesData(), org.apache.plc4x.java.spi.generation.ByteOrder.LITTLE_ENDIAN);
+            int total = (int) read.getTotalBytes();
+            for (int i = 0; i < nb; i++) {
+                int length = 0;
+                int offset = responses.getOffsets().get(i) - responses.getOffsets().get(0); //Substract first offset as we only have the service in the buffer (not servicesNb and offsets)
+                if (i == nb - 1) {
+                    length = total - offset; //Get the rest if last
+                } else {
+                    length = responses.getOffsets().get(i + 1) - offset - responses.getOffsets().get(0); //Calculate length with offsets (substracting first offset)
+                }
+                ReadBuffer serviceBuf = new ReadBufferByteBased(read.getBytes(offset, offset + length), org.apache.plc4x.java.spi.generation.ByteOrder.LITTLE_ENDIAN);
+                CipService service = null;
+                try {
+                    service = CipService.staticParse(read, length);
+                    arr.add(service);
+                } catch (ParseException e) {
+                    throw new PlcRuntimeException(e);
+                }
+            }
+            Services services = new Services(nb, responses.getOffsets(), arr, -1);
+            Iterator<String> it = readRequest.getFieldNames().iterator();
+            for (int i = 0; i < nb && it.hasNext(); i++) {
+                String fieldName = it.next();
+                LogixField field = (LogixField) readRequest.getField(fieldName);
+                PlcValue plcValue = null;
+                if (services.getServices().get(i) instanceof CipReadResponse) {
+                    CipReadResponse readResponse = (CipReadResponse) services.getServices().get(i);
+                    PlcResponseCode code;
+                    if (readResponse.getStatus() == 0) {
+                        code = PlcResponseCode.OK;
+                    } else {
+                        code = PlcResponseCode.INTERNAL_ERROR;
+                    }
+                    CIPDataTypeCode type = readResponse.getDataType();
+                    ByteBuf data = Unpooled.wrappedBuffer(readResponse.getData());
+                    if (code == PlcResponseCode.OK) {
+                        plcValue = parsePlcValue(field, data, type);
+                    }
+                    ResponseItem<PlcValue> result = new ResponseItem<>(code, plcValue);
+                    values.put(fieldName, result);
+                }
+            }
+        }
+        return new DefaultPlcReadResponse(readRequest, values);
+    }
+
+    private PlcValue parsePlcValue(LogixField field, ByteBuf data, CIPDataTypeCode type) {
+        int nb = field.getElementNb();
+        if (nb > 1) {
+            int index = 0;
+            List<PlcValue> list = new ArrayList<>();
+            for (int i = 0; i < nb; i++) {
+                switch (type) {
+                    case DINT:
+                        list.add(new PlcDINT(Integer.reverseBytes(data.getInt(index))));
+                        index += type.getSize();
+                        break;
+                    case INT:
+                        list.add(new PlcINT(Integer.reverseBytes(data.getInt(index))));
+                        index += type.getSize();
+                        break;
+                    case SINT:
+                        list.add(new PlcSINT(Integer.reverseBytes(data.getInt(index))));
+                        index += type.getSize();
+                        break;
+                    case REAL:
+                        list.add(new PlcLREAL(swap(data.getFloat(index))));
+                        index += type.getSize();
+                        break;
+                    case BOOL:
+                        list.add(new PlcBOOL(data.getBoolean(index)));
+                        index += type.getSize();
+                    default:
+                        return null;
+                }
+            }
+            return new PlcList(list);
+        } else {
+            switch (type) {
+                case SINT:
+                    return new PlcSINT(data.getByte(0));
+                case INT:
+                    return new PlcINT(Short.reverseBytes(data.getShort(0)));
+                case DINT:
+                    return new PlcDINT(Integer.reverseBytes(data.getInt(0)));
+                case REAL:
+                    return new PlcREAL(swap(data.getFloat(0)));
+                case BOOL:
+                    return new PlcBOOL(data.getBoolean(0));
+                default:
+                    return null;
+            }
+        }
+    }
+
+    public float swap(float value) {
+        int bytes = Float.floatToIntBits(value);
+        int b1 = (bytes >> 0) & 0xff;
+        int b2 = (bytes >> 8) & 0xff;
+        int b3 = (bytes >> 16) & 0xff;
+        int b4 = (bytes >> 24) & 0xff;
+        return Float.intBitsToFloat(b1 << 24 | b2 << 16 | b3 << 8 | b4 << 0);
+    }
+
+    @Override
+    public CompletableFuture<PlcWriteResponse> write(PlcWriteRequest writeRequest) {
+        CompletableFuture<PlcWriteResponse> future = new CompletableFuture<>();
+        DefaultPlcWriteRequest request = (DefaultPlcWriteRequest) writeRequest;
+        List<CipWriteRequest> items = new ArrayList<>(writeRequest.getNumberOfFields());
+        for (String fieldName : request.getFieldNames()) {
+            final LogixField field = (LogixField) request.getField(fieldName);
+            final PlcValue value = request.getPlcValue(fieldName);
+            String tag = field.getTag();
+            int elements = 1;
+            if (field.getElementNb() > 1) {
+                elements = field.getElementNb();
+            }
+
+            //We need the size of the request in words (0x91, tagLength, ... tag + possible pad)
+            // Taking half to get word size
+            boolean isArray = false;
+            String tagIsolated = tag;
+            if (tag.contains("[")) {
+                isArray = true;
+                tagIsolated = tag.substring(0, tag.indexOf("["));
+            }
+            int dataLength = (tagIsolated.length() + 2 + (tagIsolated.length() % 2) + (isArray ? 2 : 0));
+            byte requestPathSize = (byte) (dataLength / 2);
+            byte[] data = encodeValue(value, field.getType(), (short) elements);
+            CipWriteRequest writeReq = new CipWriteRequest(requestPathSize, toAnsi(tag), field.getType(), elements, data, -1);
+            items.add(writeReq);
+        }
+
+        RequestTransactionManager.RequestTransaction transaction = tm.startRequest();
+        if (items.size() == 1) {
+            tm.startRequest();
+            CipRRData rrdata = new CipRRData(sessionHandle, 0L, senderContext, 0L,
+                new CipExchange(
+                    new CipUnconnectedRequest(
+                        items.get(0), (byte) configuration.getBackplane(), (byte) configuration.getSlot(), -1
+                    ),
+                    -1
+                ),
+                -1
+            );
+            transaction.submit(() -> context.sendRequest(rrdata)
+                .expectResponse(EipPacket.class, REQUEST_TIMEOUT)
+                .onTimeout(future::completeExceptionally)
+                .onError((p, e) -> future.completeExceptionally(e))
+                .check(p -> p instanceof CipRRData).unwrap(p -> (CipRRData) p)
+                .check(p -> p.getSessionHandle() == sessionHandle)
+                //.check(p -> p.getSenderContext() == senderContext)
+                .check(p -> p.getExchange().getService() instanceof CipWriteResponse)
+                .unwrap(p -> (CipWriteResponse) p.getExchange().getService())
+                .handle(p -> {
+                    future.complete((PlcWriteResponse) decodeWriteResponse(p, writeRequest));
+                    transaction.endRequest();
+                })
+            );
+        } else {
+            tm.startRequest();
+            short nb = (short) items.size();
+            List<Integer> offsets = new ArrayList<>(nb);
+            int offset = 2 + nb * 2;
+            for (int i = 0; i < nb; i++) {
+                offsets.add(offset);
+                offset += items.get(i).getLengthInBytes();
+            }
+
+            List<CipService> serviceArr = new ArrayList<>(nb);
+            for (int i = 0; i < nb; i++) {
+                serviceArr.add(items.get(i));
+            }
+            Services data = new Services(nb, offsets, serviceArr, -1);
+            //Encapsulate the data
+
+            CipRRData pkt = new CipRRData(sessionHandle, 0L, emptySenderContext, 0L,
+                new CipExchange(
+                    new CipUnconnectedRequest(
+                        new MultipleServiceRequest(data, -1),
+                        (byte) configuration.getBackplane(),
+                        (byte) configuration.getSlot(),
+                        -1
+                    ),
+                    -1
+                ),
+                -1
+            );
+
+
+            transaction.submit(() -> context.sendRequest(pkt)
+                .expectResponse(EipPacket.class, REQUEST_TIMEOUT)
+                .onTimeout(future::completeExceptionally)
+                .onError((p, e) -> future.completeExceptionally(e))
+                .check(p -> p instanceof CipRRData)
+                .check(p -> p.getSessionHandle() == sessionHandle)
+                //.check(p -> p.getSenderContext() == senderContext)
+                .unwrap(p -> (CipRRData) p)
+                .unwrap(p -> p.getExchange().getService()).check(p -> p instanceof MultipleServiceResponse)
+                .unwrap(p -> (MultipleServiceResponse) p)
+                .check(p -> p.getServiceNb() == nb)
+                .handle(p -> {
+                    future.complete((PlcWriteResponse) decodeWriteResponse(p, writeRequest));
+                    // Finish the request-transaction.
+                    transaction.endRequest();
+                }));
+        }
+        return future;
+    }
+
+    private PlcResponse decodeWriteResponse(CipService p, PlcWriteRequest writeRequest) {
+        Map<String, PlcResponseCode> responses = new HashMap<>();
+
+        if (p instanceof CipWriteResponse) {
+            CipWriteResponse resp = (CipWriteResponse) p;
+            String fieldName = writeRequest.getFieldNames().iterator().next();
+            LogixField field = (LogixField) writeRequest.getField(fieldName);
+            responses.put(fieldName, decodeResponseCode(resp.getStatus()));
+            return new DefaultPlcWriteResponse(writeRequest, responses);
+        } else if (p instanceof MultipleServiceResponse) {
+            MultipleServiceResponse resp = (MultipleServiceResponse) p;
+            int nb = resp.getServiceNb();
+            List<CipService> arr = new ArrayList<>(nb);
+            ReadBufferByteBased read = new ReadBufferByteBased(resp.getServicesData());
+            int total = (int) read.getTotalBytes();
+            for (int i = 0; i < nb; i++) {
+                int length = 0;
+                int offset = resp.getOffsets().get(i);
+                if (offset == nb - 1) {
+                    length = total - offset; //Get the rest if last
+                } else {
+                    length = resp.getOffsets().get(i + 1) - offset; //Calculate length with offsets
+                }
+                ReadBuffer serviceBuf = new ReadBufferByteBased(read.getBytes(offset, length), org.apache.plc4x.java.spi.generation.ByteOrder.LITTLE_ENDIAN);
+                CipService service = null;
+                try {
+                    service = CipService.staticParse(read, length);
+                    arr.add(service);
+                } catch (ParseException e) {
+                    throw new PlcRuntimeException(e);
+                }
+            }
+            Services services = new Services(nb, resp.getOffsets(), arr, -1);
+            Iterator<String> it = writeRequest.getFieldNames().iterator();
+            for (int i = 0; i < nb && it.hasNext(); i++) {
+                String fieldName = it.next();
+                LogixField field = (LogixField) writeRequest.getField(fieldName);
+                PlcValue plcValue = null;
+                if (services.getServices().get(i) instanceof CipWriteResponse) {
+                    CipWriteResponse writeResponse = (CipWriteResponse) services.getServices().get(i);
+                    PlcResponseCode code = decodeResponseCode(writeResponse.getStatus());
+                    responses.put(fieldName, code);
+                }
+            }
+            return new DefaultPlcWriteResponse(writeRequest, responses);
+        }
+        return null;
+    }
+
+    private byte[] encodeValue(PlcValue value, CIPDataTypeCode type, short elements) {
+        //ByteBuffer buffer = ByteBuffer.allocate(4+type.getSize()).order(ByteOrder.LITTLE_ENDIAN);
+        ByteBuffer buffer = ByteBuffer.allocate(type.getSize()).order(ByteOrder.LITTLE_ENDIAN);
+        switch (type) {
+            case SINT:
+                buffer.put(value.getByte());
+                break;
+            case INT:
+                buffer.putShort(value.getShort());
+                break;
+            case DINT:
+                buffer.putInt(value.getInteger());
+                break;
+            case REAL:
+                buffer.putDouble(value.getDouble());
+                break;
+            default:
+                break;
+        }
+        return buffer.array();
+
+    }
+
+    private PlcResponseCode decodeResponseCode(int status) {
+        //TODO other status
+        switch (status) {
+            case 0:
+                return PlcResponseCode.OK;
+            default:
+                return PlcResponseCode.INTERNAL_ERROR;
+        }
+    }
+
+    @Override
+    public void close(ConversationContext<EipPacket> context) {
+        logger.debug("Sending UnregisterSession EIP Pakcet");
+        context.sendRequest(new EipDisconnectRequest(sessionHandle, 0L, emptySenderContext, 0L)); //Unregister gets no response
+        logger.debug("Unregistred Session {}", sessionHandle);
+    }
+}
diff --git a/plc4j/drivers/logix/src/main/resources/META-INF/services/org.apache.plc4x.java.api.PlcDriver b/plc4j/drivers/logix/src/main/resources/META-INF/services/org.apache.plc4x.java.api.PlcDriver
new file mode 100644
index 0000000000..c62562f371
--- /dev/null
+++ b/plc4j/drivers/logix/src/main/resources/META-INF/services/org.apache.plc4x.java.api.PlcDriver
@@ -0,0 +1,19 @@
+#
+# 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.
+#
+org.apache.plc4x.java.logix.readwrite.LogixDriver
\ No newline at end of file
diff --git a/plc4j/drivers/logix/src/test/java/org/apache/plc4x/java/eip/readwrite/LogixDriverTestsuite.java b/plc4j/drivers/logix/src/test/java/org/apache/plc4x/java/eip/readwrite/LogixDriverTestsuite.java
new file mode 100644
index 0000000000..e8d9f9bd9f
--- /dev/null
+++ b/plc4j/drivers/logix/src/test/java/org/apache/plc4x/java/eip/readwrite/LogixDriverTestsuite.java
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+package org.apache.plc4x.java.eip.readwrite;
+
+import org.apache.plc4x.test.driver.DriverTestsuiteRunner;
+
+public class LogixDriverTestsuite extends DriverTestsuiteRunner {
+
+    public LogixDriverTestsuite() {
+        super("/protocols/logix/DriverTestsuite.xml");
+    }
+
+}
diff --git a/plc4j/drivers/logix/src/test/java/org/apache/plc4x/java/eip/readwrite/LogixParserSerializerTest.java b/plc4j/drivers/logix/src/test/java/org/apache/plc4x/java/eip/readwrite/LogixParserSerializerTest.java
new file mode 100644
index 0000000000..8e22238f1e
--- /dev/null
+++ b/plc4j/drivers/logix/src/test/java/org/apache/plc4x/java/eip/readwrite/LogixParserSerializerTest.java
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+package org.apache.plc4x.java.eip.readwrite;
+
+import org.apache.plc4x.test.parserserializer.ParserSerializerTestsuiteRunner;
+
+public class LogixParserSerializerTest extends ParserSerializerTestsuiteRunner {
+
+    public LogixParserSerializerTest() {
+        super("/protocols/logix/ParserSerializerTestsuite.xml");
+    }
+
+}
diff --git a/plc4j/drivers/logix/src/test/resources/logback.xml b/plc4j/drivers/logix/src/test/resources/logback.xml
new file mode 100644
index 0000000000..4f7ce5ad5a
--- /dev/null
+++ b/plc4j/drivers/logix/src/test/resources/logback.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  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.
+  -->
+<configuration xmlns="http://ch.qos.logback/xml/ns/logback"
+               xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+               xsi:schemaLocation="
+                  http://ch.qos.logback/xml/ns/logback
+                  https://raw.githubusercontent.com/enricopulatzo/logback-XSD/master/src/main/xsd/logback.xsd">
+
+  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+    <encoder>
+      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
+    </encoder>
+  </appender>
+
+  <root level="error">
+    <appender-ref ref="STDOUT" />
+  </root>
+
+</configuration>
\ No newline at end of file
diff --git a/plc4j/drivers/pom.xml b/plc4j/drivers/pom.xml
index 21048b6dc4..036d506dad 100644
--- a/plc4j/drivers/pom.xml
+++ b/plc4j/drivers/pom.xml
@@ -43,6 +43,7 @@
     <module>eip</module>
     <module>firmata</module>
     <module>knxnetip</module>
+    <module>logix</module>
     <module>mock</module>
     <module>modbus</module>
     <module>opcua</module>
diff --git a/protocols/logix/src/main/resources/protocols/logix/logix.mspec b/protocols/logix/src/main/resources/protocols/logix/logix.mspec
index 02506377b3..efb664ea21 100644
--- a/protocols/logix/src/main/resources/protocols/logix/logix.mspec
+++ b/protocols/logix/src/main/resources/protocols/logix/logix.mspec
@@ -161,7 +161,7 @@
     [simple     uint    3   pathSegmentType]
     [simple     uint    3   logicalSegmentType]
     [simple     uint    2   logicalSegmentFormat]
-    [simple     uint    8   class]
+    [simple     uint    8   classSegment]
 ]
 
 [type   PortSegment
@@ -180,9 +180,9 @@
 ]
 
 [type   TransportType
-   [simple      bit        direction]
+   [simple      bit         direction]
    [simple      uint    3   trigger]
-   [simple      uint    4   class]
+   [simple      uint    4   classTransport]
 ]
 
 [type   Services  (uint   16   servicesLen)