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)