You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@plc4x.apache.org by cd...@apache.org on 2019/12/17 20:13:47 UTC
[plc4x] 02/02: - Continued porting the S7 driver to fully generated
drivers.
This is an automated email from the ASF dual-hosted git repository.
cdutz pushed a commit to branch next-gen-core
in repository https://gitbox.apache.org/repos/asf/plc4x.git
commit ff91b0504084471612499fc478ac8721968bc210
Author: Christofer Dutz <ch...@c-ware.de>
AuthorDate: Tue Dec 17 21:13:37 2019 +0100
- Continued porting the S7 driver to fully generated drivers.
---
.../language/java/JavaLanguageTemplateHelper.java | 24 +
.../main/resources/templates/java/io-template.ftlh | 13 +-
plc4j/examples/hello-world-plc4x/pom.xml | 7 +
plc4j/pom.xml | 1 +
.../org/apache/plc4x/java/utils/ReadBuffer.java | 8 +-
.../s7/src/main/resources/protocols/s7/s7.mspec | 38 +-
.../server/s7/protocol/S7Step7ServerProtocol.java | 2 +-
sandbox/test-java-s7-driver/pom.xml | 23 +
.../apache/plc4x/java/s7/readwrite/S7Driver.java | 74 +++
.../java/s7/readwrite/connection/S7Connection.java | 224 ++++++++
.../s7/readwrite/events/IsoTPConnectedEvent.java | 22 +
.../java/s7/readwrite/events/S7ConnectedEvent.java | 22 +
.../java/s7/readwrite/protocol/Plc4xProtocol.java | 619 +++++++++++++++++++++
.../java/s7/readwrite/protocol/S7Protocol.java | 67 +++
.../java/s7/readwrite/types/S7ControllerType.java | 30 +
.../plc4x/java/s7/readwrite/utils/S7Field.java | 265 +++++++++
.../java/s7/readwrite/utils/S7PlcFieldHandler.java | 569 +++++++++++++++++++
.../java/s7/readwrite/utils/S7TsapIdEncoder.java | 48 ++
.../services/org.apache.plc4x.java.spi.PlcDriver | 19 +
19 files changed, 2056 insertions(+), 19 deletions(-)
diff --git a/build-utils/language-java/src/main/java/org/apache/plc4x/language/java/JavaLanguageTemplateHelper.java b/build-utils/language-java/src/main/java/org/apache/plc4x/language/java/JavaLanguageTemplateHelper.java
index 2804d4b..307471b 100644
--- a/build-utils/language-java/src/main/java/org/apache/plc4x/language/java/JavaLanguageTemplateHelper.java
+++ b/build-utils/language-java/src/main/java/org/apache/plc4x/language/java/JavaLanguageTemplateHelper.java
@@ -193,6 +193,30 @@ public class JavaLanguageTemplateHelper implements FreemarkerLanguageTemplateHel
return "Hurz";
}
+ public int getNumBits(SimpleTypeReference simpleTypeReference) {
+ switch (simpleTypeReference.getBaseType()) {
+ case BIT: {
+ return 1;
+ }
+ case UINT:
+ case INT: {
+ IntegerTypeReference integerTypeReference = (IntegerTypeReference) simpleTypeReference;
+ return integerTypeReference.getSizeInBits();
+ }
+ case FLOAT: {
+ FloatTypeReference floatTypeReference = (FloatTypeReference) simpleTypeReference;
+ return floatTypeReference.getSizeInBits();
+ }
+ case STRING: {
+ IntegerTypeReference integerTypeReference = (IntegerTypeReference) simpleTypeReference;
+ return integerTypeReference.getSizeInBits();
+ }
+ default: {
+ return 0;
+ }
+ }
+ }
+
public String getReadBufferReadMethodCall(SimpleTypeReference simpleTypeReference) {
switch (simpleTypeReference.getBaseType()) {
case BIT: {
diff --git a/build-utils/language-java/src/main/resources/templates/java/io-template.ftlh b/build-utils/language-java/src/main/resources/templates/java/io-template.ftlh
index aa26563..9866247 100644
--- a/build-utils/language-java/src/main/resources/templates/java/io-template.ftlh
+++ b/build-utils/language-java/src/main/resources/templates/java/io-template.ftlh
@@ -73,10 +73,13 @@ public class ${typeName}IO {
if(${helper.toDeserializationExpression(field.loopExpression, type.parserArguments)?no_esc} > Integer.MAX_VALUE) {
throw new ParseException("Array count of " + (${helper.toDeserializationExpression(field.loopExpression, type.parserArguments)?no_esc}) + " exceeds the maximum allowed count of " + Integer.MAX_VALUE);
}
- int _${field.name}Count = (int) ${helper.toDeserializationExpression(field.loopExpression, type.parserArguments)?no_esc};
- ${helper.getLanguageTypeNameForField(field)}[] ${field.name} = new ${helper.getLanguageTypeNameForField(field)}[_${field.name}Count];
- for(int i = 0; i < _${field.name}Count; i++) {
- ${field.name}[i] = <#if helper.isSimpleType(field.type)>${helper.getReadBufferReadMethodCall(field.type)?no_esc}<#else>${field.type.name}IO.parse(io<#if field.params?has_content>, <#list field.params as parserArgument>(${helper.getArgumentType(field.type, parserArgument?index)}) (${helper.toDeserializationExpression(parserArgument, type.parserArguments)?no_esc})<#sep>, </#sep></#list></#if>)</#if>;
+ ${helper.getLanguageTypeNameForField( field)}[] ${field.name};
+ {
+ int itemCount = (int) ${helper.toDeserializationExpression(field.loopExpression, type.parserArguments)?no_esc};
+ ${field.name} = new ${helper.getLanguageTypeNameForField(field)}[itemCount];
+ for(int curItem = 0; curItem < itemCount; curItem++) {
+ ${field.name}[curItem] = <#if helper.isSimpleType(field.type)>${helper.getReadBufferReadMethodCall(field.type)?no_esc}<#else>${field.type.name}IO.parse(io<#if field.params?has_content>, <#list field.params as parserArgument>(${helper.getArgumentType(field.type, parserArgument?index)}) (${helper.toDeserializationExpression(parserArgument, type.parserArguments)?no_esc})<#sep>, </#sep></#list></#if>)</#if>;
+ }
}
<#-- In all other cases do we have to work with a list, that is later converted to an array -->
<#else>
@@ -230,7 +233,7 @@ public class ${typeName}IO {
<#case "padding">
// Padding Field (${field.name})
- boolean _${field.name}NeedsPadding = (boolean) (${helper.toDeserializationExpression(field.paddingCondition, type.parserArguments)});
+ boolean _${field.name}NeedsPadding = (boolean) ((io.hasMore(${helper.getNumBits(field.type)})) && (${helper.toDeserializationExpression(field.paddingCondition, type.parserArguments)}));
if(_${field.name}NeedsPadding) {
// Just read the padding data and ignore it
${helper.getReadBufferReadMethodCall(field.type)?no_esc};
diff --git a/plc4j/examples/hello-world-plc4x/pom.xml b/plc4j/examples/hello-world-plc4x/pom.xml
index f2e9aa5..0228ec2 100644
--- a/plc4j/examples/hello-world-plc4x/pom.xml
+++ b/plc4j/examples/hello-world-plc4x/pom.xml
@@ -104,6 +104,12 @@
<version>0.6.0-SNAPSHOT</version>
<scope>runtime</scope>
</dependency>
+ <dependency>
+ <groupId>org.apache.plc4x.sandbox</groupId>
+ <artifactId>test-java-s7-driver</artifactId>
+ <version>0.6.0-SNAPSHOT</version>
+ <scope>runtime</scope>
+ </dependency>
</dependencies>
<build>
@@ -120,6 +126,7 @@
<usedDependency>org.apache.plc4x:plc4j-driver-opcua</usedDependency>
<usedDependency>org.apache.plc4x:plc4j-driver-s7</usedDependency>
<usedDependency>org.apache.plc4x:plc4j-driver-simulated</usedDependency>
+ <usedDependency>org.apache.plc4x.sandbox:test-java-s7-driver</usedDependency>
<usedDependency>org.slf4j:log4j-over-slf4j</usedDependency>
</usedDependencies>
</configuration>
diff --git a/plc4j/pom.xml b/plc4j/pom.xml
index 080ce71..1da9579 100644
--- a/plc4j/pom.xml
+++ b/plc4j/pom.xml
@@ -37,6 +37,7 @@
<module>api</module>
<module>drivers</module>
<module>utils</module>
+ <module>protocols</module>
<module>examples</module>
<module>integrations</module>
diff --git a/plc4j/utils/driver-base-java/src/main/java/org/apache/plc4x/java/utils/ReadBuffer.java b/plc4j/utils/driver-base-java/src/main/java/org/apache/plc4x/java/utils/ReadBuffer.java
index e4fd993..10db633 100644
--- a/plc4j/utils/driver-base-java/src/main/java/org/apache/plc4x/java/utils/ReadBuffer.java
+++ b/plc4j/utils/driver-base-java/src/main/java/org/apache/plc4x/java/utils/ReadBuffer.java
@@ -30,6 +30,7 @@ public class ReadBuffer {
private final MyDefaultBitInput bi;
private final boolean littleEndian;
+ private final long totalBytes;
public ReadBuffer(byte[] input) {
this(input, true);
@@ -37,14 +38,19 @@ public class ReadBuffer {
public ReadBuffer(byte[] input, boolean littleEndian) {
ArrayByteInput abi = new ArrayByteInput(input);
- bi = new MyDefaultBitInput(abi);
+ this.bi = new MyDefaultBitInput(abi);
this.littleEndian = littleEndian;
+ this.totalBytes = input.length * 8;
}
public int getPos() {
return (int) bi.getPos();
}
+ public boolean hasMore(int numBits) {
+ return (numBits / 8) > (totalBytes - getPos());
+ }
+
public byte peekByte(int offset) throws ParseException {
// Remember the old index.
int oldIndex = bi.getDelegate().getIndex();
diff --git a/protocols/s7/src/main/resources/protocols/s7/s7.mspec b/protocols/s7/src/main/resources/protocols/s7/s7.mspec
index 31b588c..9867c69 100644
--- a/protocols/s7/src/main/resources/protocols/s7/s7.mspec
+++ b/protocols/s7/src/main/resources/protocols/s7/s7.mspec
@@ -228,23 +228,23 @@
// This is actually not quite correct as depending pon the transportSize the length is either defined in bits or bytes.
[type 'S7VarPayloadDataItem'
- [simple uint 8 'returnCode']
- [enum DataTransportSize 'transportSize']
- [simple uint 16 'dataLength']
- [array uint 8 'data' count 'dataLength / 8']
- [padding uint 8 'pad' '0x00' '(dataLength / 8) % 2 == 1']
+ [enum DataTransportErrorCode 'returnCode']
+ [enum DataTransportSize 'transportSize']
+ [simple uint 16 'dataLength']
+ [array int 8 'data' count 'dataLength / 8']
+ [padding uint 8 'pad' '0x00' '(dataLength / 8) % 2 == 1']
]
[type 'S7VarPayloadStatusItem'
- [simple uint 8 'returnCode']
+ [enum DataTransportErrorCode 'returnCode']
]
[discriminatedType 'S7PayloadUserDataItem' [uint 4 'cpuFunctionType']
- [simple uint 8 'returnCode']
- [enum DataTransportSize 'transportSize']
- [implicit uint 16 'dataLength' 'lengthInBytes - 4']
- [simple SzlId 'szlId']
- [simple uint 16 'szlIndex']
+ [enum DataTransportErrorCode 'returnCode']
+ [enum DataTransportSize 'transportSize']
+ [implicit uint 16 'dataLength' 'lengthInBytes - 4']
+ [simple SzlId 'szlId']
+ [simple uint 16 'szlIndex']
[typeSwitch 'cpuFunctionType'
['0x04' S7PayloadUserDataItemCpuFunctionReadSzlRequest
]
@@ -256,7 +256,6 @@
]
]
-
[enum int 8 'COTPTpduSize' [uint 8 'sizeInBytes']
['0x07' SIZE_128 ['128']]
['0x08' SIZE_256 ['256']]
@@ -285,6 +284,12 @@
['0x09' OCTET_STRING ['false']]
]
+[enum int 8 'DeviceGroup'
+ ['0x01' PG_OR_PC]
+ ['0x02' OS ]
+ ['0x03' OTHERS ]
+]
+
[enum int 8 'TransportSize' [uint 8 'sizeCode', uint 8 'sizeInBytes', TransportSize 'baseType', DataTransportSize 'dataTransportSize']
['0x01' BOOL ['X' , '1' , 'null' , 'DataTransportSize.BIT']]
['0x02' BYTE ['B' , '1' , 'null' , 'DataTransportSize.BYTE_WORD_DWORD']]
@@ -334,6 +339,15 @@
['0x09' OCTET_STRING ['false']]
]
+[enum int 8 'DataTransportErrorCode'
+ ['0x00' RESERVED ]
+ ['0xFF' OK ]
+ ['0x03' ACCESS_DENIED ]
+ ['0x05' INVALID_ADDRESS ]
+ ['0x06' DATA_TYPE_NOT_SUPPORTED]
+ ['0x0A' NOT_FOUND ]
+]
+
[enum int 4 'SzlModuleTypeClass'
['0x0' CPU]
['0x4' IM]
diff --git a/sandbox/plc-simulator/src/main/java/org/apache/plc4x/simulator/server/s7/protocol/S7Step7ServerProtocol.java b/sandbox/plc-simulator/src/main/java/org/apache/plc4x/simulator/server/s7/protocol/S7Step7ServerProtocol.java
index a166538..35a3142 100644
--- a/sandbox/plc-simulator/src/main/java/org/apache/plc4x/simulator/server/s7/protocol/S7Step7ServerProtocol.java
+++ b/sandbox/plc-simulator/src/main/java/org/apache/plc4x/simulator/server/s7/protocol/S7Step7ServerProtocol.java
@@ -177,7 +177,7 @@ public class S7Step7ServerProtocol extends ChannelInboundHandlerAdapter {
S7PayloadUserDataItemCpuFunctionReadSzlResponse readSzlResponsePayload =
new S7PayloadUserDataItemCpuFunctionReadSzlResponse(
- (short) 0xFF, DataTransportSize.OCTET_STRING, szlId,
+ DataTransportErrorCode.OK, DataTransportSize.OCTET_STRING, szlId,
readSzlRequestPayload.getSzlIndex(), items);
S7ParameterUserDataItem[] responseParameterItems =
diff --git a/sandbox/test-java-s7-driver/pom.xml b/sandbox/test-java-s7-driver/pom.xml
index 1cf9911..880f753 100644
--- a/sandbox/test-java-s7-driver/pom.xml
+++ b/sandbox/test-java-s7-driver/pom.xml
@@ -56,15 +56,38 @@
<dependencies>
<dependency>
<groupId>org.apache.plc4x</groupId>
+ <artifactId>plc4j-api</artifactId>
+ <version>0.6.0-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.plc4x</groupId>
<artifactId>plc4j-utils-driver-base-java</artifactId>
<version>0.6.0-SNAPSHOT</version>
</dependency>
<dependency>
+ <groupId>org.apache.plc4x</groupId>
+ <artifactId>plc4j-protocol-driver-base</artifactId>
+ <version>0.6.0-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.plc4x</groupId>
+ <artifactId>plc4j-protocol-driver-base-tcp</artifactId>
+ <version>0.6.0-SNAPSHOT</version>
+ </dependency>
+
+ <dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
<dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>osgi.cmpn</artifactId>
+ <version>6.0.0</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<scope>test</scope>
diff --git a/sandbox/test-java-s7-driver/src/main/java/org/apache/plc4x/java/s7/readwrite/S7Driver.java b/sandbox/test-java-s7-driver/src/main/java/org/apache/plc4x/java/s7/readwrite/S7Driver.java
new file mode 100644
index 0000000..6daf337
--- /dev/null
+++ b/sandbox/test-java-s7-driver/src/main/java/org/apache/plc4x/java/s7/readwrite/S7Driver.java
@@ -0,0 +1,74 @@
+/*
+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.s7.readwrite;
+
+import org.apache.plc4x.java.api.PlcConnection;
+import org.apache.plc4x.java.api.authentication.PlcAuthentication;
+import org.apache.plc4x.java.api.exceptions.PlcConnectionException;
+import org.apache.plc4x.java.s7.readwrite.connection.S7Connection;
+import org.apache.plc4x.java.spi.PlcDriver;
+import org.osgi.service.component.annotations.Component;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+@Component(service = PlcDriver.class, immediate = true)
+public class S7Driver implements PlcDriver {
+
+ private static final Pattern S7_URI_PATTERN = Pattern.compile("^s7ng://(?<host>.*)(?<params>\\?.*)?");
+
+ @Override
+ public String getProtocolCode() {
+ return "s7ng";
+ }
+
+ @Override
+ public String getProtocolName() {
+ return "Siemens S7 (Basic)";
+ }
+
+ @Override
+ public PlcConnection connect(String url) throws PlcConnectionException {
+ Matcher matcher = S7_URI_PATTERN.matcher(url);
+ if (!matcher.matches()) {
+ throw new PlcConnectionException(
+ "Connection url doesn't match the format 's7ng://{host|ip}'");
+ }
+ String host = matcher.group("host");
+
+ String params = matcher.group("params") != null ? matcher.group("params").substring(1) : null;
+
+ try {
+ InetAddress serverInetAddress = InetAddress.getByName(host);
+ return new S7Connection(serverInetAddress, params);
+ } catch (UnknownHostException e) {
+ throw new PlcConnectionException("Error parsing address", e);
+ } catch (Exception e) {
+ throw new PlcConnectionException("Error connecting to host", e);
+ }
+ }
+
+ @Override
+ public PlcConnection connect(String url, PlcAuthentication authentication) throws PlcConnectionException {
+ throw new PlcConnectionException("Basic S7 connections don't support authentication (NG).");
+ }
+
+}
diff --git a/sandbox/test-java-s7-driver/src/main/java/org/apache/plc4x/java/s7/readwrite/connection/S7Connection.java b/sandbox/test-java-s7-driver/src/main/java/org/apache/plc4x/java/s7/readwrite/connection/S7Connection.java
new file mode 100644
index 0000000..8616533
--- /dev/null
+++ b/sandbox/test-java-s7-driver/src/main/java/org/apache/plc4x/java/s7/readwrite/connection/S7Connection.java
@@ -0,0 +1,224 @@
+/*
+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.s7.readwrite.connection;
+
+import io.netty.channel.*;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.plc4x.java.api.messages.PlcReadRequest;
+import org.apache.plc4x.java.api.messages.PlcReadResponse;
+import org.apache.plc4x.java.api.messages.PlcWriteRequest;
+import org.apache.plc4x.java.api.messages.PlcWriteResponse;
+import org.apache.plc4x.java.base.connection.ChannelFactory;
+import org.apache.plc4x.java.base.connection.NettyPlcConnection;
+import org.apache.plc4x.java.base.events.ConnectEvent;
+import org.apache.plc4x.java.base.events.ConnectedEvent;
+import org.apache.plc4x.java.base.messages.*;
+import org.apache.plc4x.java.s7.readwrite.protocol.Plc4xProtocol;
+import org.apache.plc4x.java.s7.readwrite.protocol.S7Protocol;
+import org.apache.plc4x.java.s7.readwrite.types.COTPTpduSize;
+import org.apache.plc4x.java.s7.readwrite.types.DeviceGroup;
+import org.apache.plc4x.java.s7.readwrite.types.S7ControllerType;
+import org.apache.plc4x.java.s7.readwrite.utils.S7PlcFieldHandler;
+import org.apache.plc4x.java.s7.readwrite.utils.S7TsapIdEncoder;
+import org.apache.plc4x.java.tcp.connection.TcpSocketChannelFactory;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.net.InetAddress;
+import java.util.concurrent.CompletableFuture;
+
+public class S7Connection extends NettyPlcConnection implements PlcReader, PlcWriter {
+
+ private static final int ISO_ON_TCP_PORT = 102;
+
+ private static final Logger logger = LoggerFactory.getLogger(S7Connection.class);
+
+ private final short rack;
+ private final short slot;
+ private final COTPTpduSize tpduSize;
+ private final short maxAmqCaller;
+ private final short maxAmqCallee;
+ private final S7ControllerType controllerType;
+
+ public S7Connection(InetAddress address, String params) {
+ this(new TcpSocketChannelFactory(address, ISO_ON_TCP_PORT), params);
+ }
+
+ public S7Connection(ChannelFactory channelFactory, String params) {
+ super(channelFactory, true);
+
+ short curRack = 1;
+ short curSlot = 1;
+ short curParamPduSize = 1024;
+ short curParamMaxAmqCaller = 8;
+ short curParamMaxAmqCallee = 8;
+ S7ControllerType curParamControllerType = S7ControllerType.ANY;
+
+ if (!StringUtils.isEmpty(params)) {
+ for (String param : params.split("&")) {
+ String[] paramElements = param.split("=");
+ String paramName = paramElements[0];
+ if (paramElements.length == 2) {
+ String paramValue = paramElements[1];
+ switch (paramName) {
+ case "rack":
+ curRack = Short.parseShort(paramValue);
+ break;
+ case "slot":
+ curSlot = Short.parseShort(paramValue);
+ break;
+ case "pdu-size":
+ curParamPduSize = Short.parseShort(paramValue);
+ break;
+ case "max-amq-caller":
+ curParamMaxAmqCaller = Short.parseShort(paramValue);
+ break;
+ case "max-amq-callee":
+ curParamMaxAmqCallee = Short.parseShort(paramValue);
+ break;
+ case "controller-type":
+ curParamControllerType = S7ControllerType.valueOf(paramValue);
+ break;
+ default:
+ logger.debug("Unknown parameter {} with value {}", paramName, paramValue);
+ }
+ } else {
+ logger.debug("Unknown no-value parameter {}", paramName);
+ }
+ }
+ }
+
+ // It seems that the LOGO devices are a little picky about the pdu-size.
+ // Instead of handling this out, they just hang up without any error message.
+ // So in case of a LOGO controller, set this to a known working value.
+ if(curParamControllerType == S7ControllerType.LOGO && curParamPduSize == 1024) {
+ curParamPduSize = 480;
+ }
+
+ this.tpduSize = getNearestMatchingTpduSize(curParamPduSize);
+
+ this.rack = curRack;
+ this.slot = curSlot;
+ this.maxAmqCallee = curParamMaxAmqCallee;
+ this.maxAmqCaller = curParamMaxAmqCaller;
+ this.controllerType = curParamControllerType;
+
+ logger.info("Setting up S7 Connection with: rack {}, slot {}, tpdu-size {}, max-amq-caller {}, " +
+ "max-amq-callee {}", rack, slot, tpduSize, maxAmqCaller, maxAmqCallee);
+ }
+
+ @Override
+ public boolean canRead() {
+ return true;
+ }
+
+ @Override
+ public boolean canWrite() {
+ return true;
+ }
+
+ @Override
+ protected ChannelHandler getChannelHandler(CompletableFuture<Void> sessionSetupCompleteFuture) {
+ short calledTsapId = S7TsapIdEncoder.encodeS7TsapId(DeviceGroup.OS, 0, 0);
+ short callingTsapId = S7TsapIdEncoder.encodeS7TsapId(DeviceGroup.PG_OR_PC, rack, slot);
+
+ return new ChannelInitializer() {
+ @Override
+ protected void initChannel(Channel channel) {
+ // Build the protocol stack for communicating with the s7 protocol.
+ ChannelPipeline pipeline = channel.pipeline();
+ pipeline.addLast(new ChannelInboundHandlerAdapter() {
+ @Override
+ public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
+ if (evt instanceof ConnectedEvent) {
+ sessionSetupCompleteFuture.complete(null);
+ } else {
+ super.userEventTriggered(ctx, evt);
+ }
+ }
+ });
+ pipeline.addLast(new S7Protocol());
+ pipeline.addLast(new Plc4xProtocol(callingTsapId, calledTsapId, tpduSize,
+ maxAmqCaller, maxAmqCallee, controllerType));
+ }
+ };
+ }
+
+ @Override
+ public PlcReadRequest.Builder readRequestBuilder() {
+ return new DefaultPlcReadRequest.Builder(this, new S7PlcFieldHandler());
+ }
+
+ @Override
+ public PlcWriteRequest.Builder writeRequestBuilder() {
+ return new DefaultPlcWriteRequest.Builder(this, new S7PlcFieldHandler());
+ }
+
+ @Override
+ public CompletableFuture<PlcReadResponse> read(PlcReadRequest readRequest) {
+ InternalPlcReadRequest internalReadRequest = checkInternal(readRequest, InternalPlcReadRequest.class);
+ CompletableFuture<InternalPlcReadResponse> future = new CompletableFuture<>();
+ PlcRequestContainer<InternalPlcReadRequest, InternalPlcReadResponse> container =
+ new PlcRequestContainer<>(internalReadRequest, future);
+ channel.writeAndFlush(container).addListener(f -> {
+ if (!f.isSuccess()) {
+ future.completeExceptionally(f.cause());
+ }
+ });
+ return future
+ .thenApply(PlcReadResponse.class::cast);
+ }
+
+ @Override
+ public CompletableFuture<PlcWriteResponse> write(PlcWriteRequest writeRequest) {
+ InternalPlcWriteRequest internalWriteRequest = checkInternal(writeRequest, InternalPlcWriteRequest.class);
+ CompletableFuture<InternalPlcWriteResponse> future = new CompletableFuture<>();
+ PlcRequestContainer<InternalPlcWriteRequest, InternalPlcWriteResponse> container =
+ new PlcRequestContainer<>(internalWriteRequest, future);
+ channel.writeAndFlush(container).addListener(f -> {
+ if (!f.isSuccess()) {
+ future.completeExceptionally(f.cause());
+ }
+ });
+ return future
+ .thenApply(PlcWriteResponse.class::cast);
+ }
+
+ @Override
+ protected void sendChannelCreatedEvent() {
+ logger.trace("Channel was created, firing ChannelCreated Event");
+ // Send an event to the pipeline telling the Protocol filters what's going on.
+ channel.pipeline().fireUserEventTriggered(new ConnectEvent());
+ }
+
+ /**
+ * Iterate over all values until one is found that the given tpdu size will fit.
+ * @param tpduSizeParameter requested tpdu size.
+ * @return smallest {@link COTPTpduSize} which will fit a given size of tpdu.
+ */
+ protected COTPTpduSize getNearestMatchingTpduSize(short tpduSizeParameter) {
+ for (COTPTpduSize value : COTPTpduSize.values()) {
+ if(value.getSizeInBytes() >= tpduSizeParameter) {
+ return value;
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/sandbox/test-java-s7-driver/src/main/java/org/apache/plc4x/java/s7/readwrite/events/IsoTPConnectedEvent.java b/sandbox/test-java-s7-driver/src/main/java/org/apache/plc4x/java/s7/readwrite/events/IsoTPConnectedEvent.java
new file mode 100644
index 0000000..e829c5b
--- /dev/null
+++ b/sandbox/test-java-s7-driver/src/main/java/org/apache/plc4x/java/s7/readwrite/events/IsoTPConnectedEvent.java
@@ -0,0 +1,22 @@
+/*
+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.s7.readwrite.events;
+
+public class IsoTPConnectedEvent {
+}
diff --git a/sandbox/test-java-s7-driver/src/main/java/org/apache/plc4x/java/s7/readwrite/events/S7ConnectedEvent.java b/sandbox/test-java-s7-driver/src/main/java/org/apache/plc4x/java/s7/readwrite/events/S7ConnectedEvent.java
new file mode 100644
index 0000000..ac22e85
--- /dev/null
+++ b/sandbox/test-java-s7-driver/src/main/java/org/apache/plc4x/java/s7/readwrite/events/S7ConnectedEvent.java
@@ -0,0 +1,22 @@
+/*
+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.s7.readwrite.events;
+
+public class S7ConnectedEvent {
+}
diff --git a/sandbox/test-java-s7-driver/src/main/java/org/apache/plc4x/java/s7/readwrite/protocol/Plc4xProtocol.java b/sandbox/test-java-s7-driver/src/main/java/org/apache/plc4x/java/s7/readwrite/protocol/Plc4xProtocol.java
new file mode 100644
index 0000000..d4e2eff
--- /dev/null
+++ b/sandbox/test-java-s7-driver/src/main/java/org/apache/plc4x/java/s7/readwrite/protocol/Plc4xProtocol.java
@@ -0,0 +1,619 @@
+/*
+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.s7.readwrite.protocol;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import io.netty.channel.ChannelHandlerContext;
+import org.apache.commons.lang3.tuple.ImmutablePair;
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.plc4x.java.api.exceptions.PlcProtocolException;
+import org.apache.plc4x.java.api.exceptions.PlcRuntimeException;
+import org.apache.plc4x.java.api.messages.PlcResponse;
+import org.apache.plc4x.java.api.model.PlcField;
+import org.apache.plc4x.java.api.types.PlcResponseCode;
+import org.apache.plc4x.java.base.PlcMessageToMessageCodec;
+import org.apache.plc4x.java.base.events.ConnectEvent;
+import org.apache.plc4x.java.base.events.ConnectedEvent;
+import org.apache.plc4x.java.base.messages.*;
+import org.apache.plc4x.java.base.messages.items.*;
+import org.apache.plc4x.java.s7.readwrite.*;
+import org.apache.plc4x.java.s7.readwrite.events.IsoTPConnectedEvent;
+import org.apache.plc4x.java.s7.readwrite.types.*;
+import org.apache.plc4x.java.s7.readwrite.utils.S7Field;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.lang.reflect.Array;
+import java.math.BigInteger;
+import java.nio.charset.StandardCharsets;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.time.temporal.ChronoUnit;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+
+public class Plc4xProtocol extends PlcMessageToMessageCodec<TPKTPacket, PlcRequestContainer> {
+
+ private static final Logger logger = LoggerFactory.getLogger(Plc4xProtocol.class);
+
+ private final int callingTsapId;
+ private int calledTsapId;
+ private COTPTpduSize cotpTpduSize;
+ private int pduSize;
+ private int maxAmqCaller;
+ private int maxAmqCallee;
+ private S7ControllerType controllerType;
+
+ private static final AtomicInteger tpduGenerator = new AtomicInteger(10);
+ private final Map<Integer, PlcRequestContainer> requests;
+
+ public Plc4xProtocol(int callingTsapId, int calledTsapId, COTPTpduSize tpduSize,
+ int maxAmqCaller, int maxAmqCallee, S7ControllerType controllerType) {
+ this.callingTsapId = callingTsapId;
+ this.calledTsapId = calledTsapId;
+ this.cotpTpduSize = tpduSize;
+ this.pduSize = tpduSize.getSizeInBytes() - 16;
+ this.maxAmqCaller = maxAmqCaller;
+ this.maxAmqCallee = maxAmqCallee;
+ this.controllerType = controllerType;
+
+ requests = new HashMap<>();
+ }
+
+ /**
+ * If the S7 protocol layer is used over Iso TP, then after receiving a {@link IsoTPConnectedEvent} the
+ * corresponding S7 setup communication message has to be sent in order to negotiate the S7 protocol layer.
+ *
+ * @param ctx the current protocol layers context
+ * @param evt the event
+ * @throws Exception throws an exception if something goes wrong internally
+ */
+ @Override
+ public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
+ // If the connection has just been established, start setting up the connection
+ // by sending a connection request to the plc.
+ if (evt instanceof ConnectEvent) {
+ logger.debug("ISO Transport Protocol Sending Connection Request");
+ // Open the session on ISO Transport Protocol first.
+ COTPPacketConnectionRequest connectionRequest = new COTPPacketConnectionRequest(
+ new COTPParameter[] {
+ new COTPParameterCalledTsap(calledTsapId),
+ new COTPParameterCallingTsap(callingTsapId),
+ new COTPParameterTpduSize(cotpTpduSize)
+ }, null, (short) 0x0000, (short) 0x000F, COTPProtocolClass.CLASS_0);
+ TPKTPacket packet = new TPKTPacket(connectionRequest);
+ ctx.channel().writeAndFlush(packet);
+ }
+ else {
+ super.userEventTriggered(ctx, evt);
+ }
+ }
+
+ @Override
+ protected void encode(ChannelHandlerContext ctx, PlcRequestContainer msg, List<Object> out) throws Exception {
+ if(msg.getRequest() instanceof DefaultPlcReadRequest) {
+ DefaultPlcReadRequest request = (DefaultPlcReadRequest) msg.getRequest();
+ List<S7VarRequestParameterItem> requestItems = new ArrayList<>(request.getNumberOfFields());
+ for (PlcField field : request.getFields()) {
+ requestItems.add(new S7VarRequestParameterItemAddress(toS7Address(field)));
+ }
+ final int tpduId = tpduGenerator.getAndIncrement();
+ out.add(new TPKTPacket(new COTPPacketData(null,
+ new S7MessageRequest(tpduId,
+ new S7ParameterReadVarRequest(requestItems.toArray(new S7VarRequestParameterItem[0])),
+ new S7PayloadReadVarRequest()),
+ true, (short) tpduId)));
+ requests.put(tpduId, msg);
+/* } else if(msg.getRequest() instanceof DefaultPlcWriteRequest) {
+ DefaultPlcWriteRequest request = (DefaultPlcWriteRequest) msg.getRequest();
+ List<S7VarRequestParameterItem> requestItems = new ArrayList<>(request.getNumberOfFields());
+ List<S7VarPayloadDataItem> payloadItems = new ArrayList<>(request.getNumberOfFields());
+ for (PlcField field : request.getFields()) {
+ requestItems.add(new S7VarRequestParameterItemAddress(toS7Address(field)));
+ payloadItems.add((new S7VarPayloadDataItem(0xff, DataTransportSize.BYTE_WORD_DWORD, 1, field)));
+ }
+ final int tpduId = tpduGenerator.getAndIncrement();
+ out.add(new TPKTPacket(new COTPPacketData(null,
+ new S7MessageRequest(3,
+ new S7ParameterWriteVarRequest(requestItems.toArray(new S7VarRequestParameterItem[0])),
+ new S7PayloadWriteVarRequest(payloadItems.toArray(new S7VarPayloadDataItem[0]))),
+ true, (short) 0)));
+ requests.put((short) tpduId, msg);*/
+ }
+ }
+
+ @Override
+ protected void decode(ChannelHandlerContext ctx, TPKTPacket msg, List<Object> out) throws Exception {
+ if((msg == null) || (msg.getPayload() == null)) {
+ return;
+ }
+
+ // When getting a response to the connection request on COTP layer, extract some
+ // data and continue logging in on the S7 protocol.
+ if(msg.getPayload() instanceof COTPPacketConnectionResponse) {
+ COTPPacketConnectionResponse cotpPacketConnectionResponse = (COTPPacketConnectionResponse) msg.getPayload();
+ for (COTPParameter parameter : cotpPacketConnectionResponse.getParameters()) {
+ if(parameter instanceof COTPParameterCalledTsap) {
+ COTPParameterCalledTsap cotpParameterCalledTsap = (COTPParameterCalledTsap) parameter;
+ calledTsapId = cotpParameterCalledTsap.getTsapId();
+ } else if(parameter instanceof COTPParameterTpduSize) {
+ COTPParameterTpduSize cotpParameterTpduSize = (COTPParameterTpduSize) parameter;
+ cotpTpduSize = cotpParameterTpduSize.getTpduSize();
+ } else if(parameter instanceof COTPParameterCallingTsap) {
+ // Ignore this ...
+ } else {
+ logger.warn("Got unknown parameter type '" + parameter.getClass().getName() + "'");
+ }
+ }
+
+ // Send an S7 login message.
+ S7ParameterSetupCommunication s7ParameterSetupCommunication =
+ new S7ParameterSetupCommunication(maxAmqCaller, maxAmqCallee, pduSize);
+ S7Message s7Message = new S7MessageRequest(0, s7ParameterSetupCommunication,
+ new S7PayloadSetupCommunication());
+ COTPPacketData cotpPacketData = new COTPPacketData(null, s7Message, true, (short) 1);
+ TPKTPacket tpktPacket = new TPKTPacket(cotpPacketData);
+ ctx.channel().writeAndFlush(tpktPacket);
+ }
+
+ else if(msg.getPayload() instanceof COTPPacketData) {
+ COTPPacketData packetData = (COTPPacketData) msg.getPayload();
+ if(packetData.getPayload() instanceof S7MessageResponse) {
+ S7MessageResponse s7MessageResponse = (S7MessageResponse) packetData.getPayload();
+ final S7Parameter parameter = s7MessageResponse.getParameter();
+ if(parameter instanceof S7ParameterSetupCommunication) {
+ S7ParameterSetupCommunication setupCommunication = (S7ParameterSetupCommunication) parameter;
+
+ // Save some data from the response.
+ maxAmqCaller = setupCommunication.getMaxAmqCaller();
+ maxAmqCallee = setupCommunication.getMaxAmqCallee();
+ pduSize = setupCommunication.getPduLength();
+
+ // Only if the controller type is set to "ANY", then try to identify the PLC type.
+ if(controllerType == S7ControllerType.ANY) {
+ // Prepare a message to request the remote to identify itself.
+ S7MessageUserData identifyRemoteMessage = new S7MessageUserData(1, new S7ParameterUserData(new S7ParameterUserDataItem[] {
+ new S7ParameterUserDataItemCPUFunctions((short) 0x11, (byte) 0x4, (byte) 0x4, (short) 0x01, (short) 0x00, null, null, null)
+ }), new S7PayloadUserData( new S7PayloadUserDataItem[] {
+ new S7PayloadUserDataItemCpuFunctionReadSzlRequest(DataTransportErrorCode.OK, DataTransportSize.OCTET_STRING, new SzlId(SzlModuleTypeClass.CPU, (byte) 0x00, SzlSublist.MODULE_IDENTIFICATION), 0x0000)
+ }));
+ COTPPacketData cotpPacketData = new COTPPacketData(null, identifyRemoteMessage, true, (short) 2);
+ TPKTPacket tpktPacket = new TPKTPacket(cotpPacketData);
+ ctx.channel().writeAndFlush(tpktPacket);
+ } else {
+ // Send an event that connection setup is complete.
+ ctx.channel().pipeline().fireUserEventTriggered(new ConnectedEvent());
+ }
+ } else if (parameter instanceof S7ParameterReadVarResponse) {
+ final PlcRequestContainer requestContainer = requests.remove(s7MessageResponse.getTpduReference());
+ final PlcResponse response = decodeReadResponse(s7MessageResponse, requestContainer);
+ requestContainer.getResponseFuture().complete(response);
+ } else if (parameter instanceof S7ParameterWriteVarResponse) {
+ S7ParameterWriteVarResponse writeResponseParameter = (S7ParameterWriteVarResponse) parameter;
+
+ System.out.println(writeResponseParameter);
+ }
+ } else if(packetData.getPayload() instanceof S7MessageUserData) {
+ S7MessageUserData messageUserData = (S7MessageUserData) packetData.getPayload();
+ if(messageUserData.getPayload() instanceof S7PayloadUserData) {
+ S7PayloadUserData payloadUserData = (S7PayloadUserData) messageUserData.getPayload();
+ for (S7PayloadUserDataItem item : payloadUserData.getItems()) {
+ if(item instanceof S7PayloadUserDataItemCpuFunctionReadSzlResponse) {
+ S7PayloadUserDataItemCpuFunctionReadSzlResponse readSzlResponseItem =
+ (S7PayloadUserDataItemCpuFunctionReadSzlResponse) item;
+ for (SzlDataTreeItem readSzlResponseItemItem : readSzlResponseItem.getItems()) {
+ if(readSzlResponseItemItem.getItemIndex() == 0x0001) {
+ final String articleNumber = new String(readSzlResponseItemItem.getMlfb());
+ controllerType = lookupControllerType(articleNumber);
+
+ // Send an event that connection setup is complete.
+ ctx.channel().pipeline().fireUserEventTriggered(new ConnectedEvent());
+ }
+ }
+ }
+ }
+ }
+ } else {
+ System.out.println(packetData);
+ }
+ }
+
+ else {
+ System.out.println(msg);
+ }
+ }
+
+ private PlcResponse decodeReadResponse(S7MessageResponse responseMessage, PlcRequestContainer requestContainer) throws PlcProtocolException {
+ InternalPlcReadRequest plcReadRequest = (InternalPlcReadRequest) requestContainer.getRequest();
+
+ S7PayloadReadVarResponse payload = (S7PayloadReadVarResponse) responseMessage.getPayload();
+
+ // If the numbers of items don't match, we're in big trouble as the only
+ // way to know how to interpret the responses is by aligning them with the
+ // items from the request as this information is not returned by the PLC.
+ if (plcReadRequest.getNumberOfFields() != payload.getItems().length) {
+ throw new PlcProtocolException(
+ "The number of requested items doesn't match the number of returned items");
+ }
+
+ Map<String, Pair<PlcResponseCode, BaseDefaultFieldItem>> values = new HashMap<>();
+ S7VarPayloadDataItem[] payloadItems = payload.getItems();
+ int index = 0;
+ for (String fieldName : plcReadRequest.getFieldNames()) {
+ S7Field field = (S7Field) plcReadRequest.getField(fieldName);
+ S7VarPayloadDataItem payloadItem = payloadItems[index];
+
+ PlcResponseCode responseCode = decodeResponseCode(payloadItem.getReturnCode());
+ BaseDefaultFieldItem fieldItem = null;
+ ByteBuf data = Unpooled.wrappedBuffer(payloadItem.getData());
+ if (responseCode == PlcResponseCode.OK) {
+ try {
+ switch (field.getDataType()) {
+ // -----------------------------------------
+ // Bit
+ // -----------------------------------------
+ case BOOL:
+ fieldItem = decodeReadResponseBitField(field, data);
+ break;
+ // -----------------------------------------
+ // Bit-strings
+ // -----------------------------------------
+ case BYTE: // 1 byte
+ fieldItem = decodeReadResponseByteBitStringField(field, data);
+ break;
+ case WORD: // 2 byte (16 bit)
+ fieldItem = decodeReadResponseShortBitStringField(field, data);
+ break;
+ case DWORD: // 4 byte (32 bit)
+ fieldItem = decodeReadResponseIntegerBitStringField(field, data);
+ break;
+ case LWORD: // 8 byte (64 bit)
+ fieldItem = decodeReadResponseLongBitStringField(field, data);
+ break;
+ // -----------------------------------------
+ // Integers
+ // -----------------------------------------
+ // 8 bit:
+ case SINT:
+ fieldItem = decodeReadResponseSignedByteField(field, data);
+ break;
+ case USINT:
+ fieldItem = decodeReadResponseUnsignedByteField(field, data);
+ break;
+ // 16 bit:
+ case INT:
+ fieldItem = decodeReadResponseSignedShortField(field, data);
+ break;
+ case UINT:
+ fieldItem = decodeReadResponseUnsignedShortField(field, data);
+ break;
+ // 32 bit:
+ case DINT:
+ fieldItem = decodeReadResponseSignedIntegerField(field, data);
+ break;
+ case UDINT:
+ fieldItem = decodeReadResponseUnsignedIntegerField(field, data);
+ break;
+ // 64 bit:
+ case LINT:
+ fieldItem = decodeReadResponseSignedLongField(field, data);
+ break;
+ case ULINT:
+ fieldItem = decodeReadResponseUnsignedLongField(field, data);
+ break;
+ // -----------------------------------------
+ // Floating point values
+ // -----------------------------------------
+ case REAL:
+ fieldItem = decodeReadResponseFloatField(field, data);
+ break;
+ case LREAL:
+ fieldItem = decodeReadResponseDoubleField(field, data);
+ break;
+ // -----------------------------------------
+ // Characters & Strings
+ // -----------------------------------------
+ case CHAR: // 1 byte (8 bit)
+ fieldItem = decodeReadResponseFixedLengthStringField(1, false, data);
+ break;
+ case WCHAR: // 2 byte
+ fieldItem = decodeReadResponseFixedLengthStringField(1, true, data);
+ break;
+ case STRING:
+ fieldItem = decodeReadResponseVarLengthStringField(false, data);
+ break;
+ case WSTRING:
+ fieldItem = decodeReadResponseVarLengthStringField(true, data);
+ break;
+ // -----------------------------------------
+ // TIA Date-Formats
+ // -----------------------------------------
+ case DATE_AND_TIME:
+ fieldItem = decodeReadResponseDateAndTime(field, data);
+ break;
+ case TIME_OF_DAY:
+ fieldItem = decodeReadResponseTimeOfDay(field, data);
+ break;
+ case DATE:
+ fieldItem = decodeReadResponseDate(field, data);
+ break;
+ default:
+ throw new PlcProtocolException("Unsupported type " + field.getDataType());
+ }
+ }
+ catch (Exception e){
+ logger.warn("Some other error occurred casting field {}, FieldInformation: {}",fieldName, field,e);
+ }
+ }
+ Pair<PlcResponseCode, BaseDefaultFieldItem> result = new ImmutablePair<>(responseCode, fieldItem);
+ values.put(fieldName, result);
+ index++;
+ }
+
+ return new DefaultPlcReadResponse(plcReadRequest, values);
+ }
+
+ private PlcResponseCode decodeResponseCode(DataTransportErrorCode dataTransportErrorCode) {
+ if (dataTransportErrorCode == null) {
+ return PlcResponseCode.INTERNAL_ERROR;
+ }
+ switch (dataTransportErrorCode) {
+ case OK:
+ return PlcResponseCode.OK;
+ case NOT_FOUND:
+ return PlcResponseCode.NOT_FOUND;
+ case INVALID_ADDRESS:
+ return PlcResponseCode.INVALID_ADDRESS;
+ case DATA_TYPE_NOT_SUPPORTED:
+ return PlcResponseCode.INVALID_DATATYPE;
+ default:
+ return PlcResponseCode.INTERNAL_ERROR;
+ }
+ }
+
+ BaseDefaultFieldItem decodeReadResponseBitField(S7Field field, ByteBuf data) {
+ Boolean[] booleans = readAllValues(Boolean.class, field, i -> data.readByte() != 0x00);
+ return new DefaultBooleanFieldItem(booleans);
+ }
+
+ BaseDefaultFieldItem decodeReadResponseByteBitStringField(S7Field field, ByteBuf data) {
+ byte[] bytes = new byte[field.getNumElements()];
+ data.readBytes(bytes);
+ return decodeBitStringField(bytes);
+ }
+
+ BaseDefaultFieldItem decodeReadResponseShortBitStringField(S7Field field, ByteBuf data) {
+ byte[] bytes = new byte[field.getNumElements() * 2];
+ data.readBytes(bytes);
+ return decodeBitStringField(bytes);
+ }
+
+ BaseDefaultFieldItem decodeReadResponseIntegerBitStringField(S7Field field, ByteBuf data) {
+ byte[] bytes = new byte[field.getNumElements() * 4];
+ data.readBytes(bytes);
+ return decodeBitStringField(bytes);
+ }
+
+ BaseDefaultFieldItem decodeReadResponseLongBitStringField(S7Field field, ByteBuf data) {
+ byte[] bytes = new byte[field.getNumElements() * 8];
+ data.readBytes(bytes);
+ return decodeBitStringField(bytes);
+ }
+
+ BaseDefaultFieldItem decodeBitStringField(byte[] bytes) {
+ BitSet bitSet = BitSet.valueOf(bytes);
+ Boolean[] booleanValues = new Boolean[8 * bytes.length];
+ int k = 0;
+ for(int i = bytes.length - 1; i >= 0; i--) {
+ for(int j = 0; j < 8; j++) {
+ booleanValues[k++] = bitSet.get(8 * i + j);
+ }
+ }
+ return new DefaultBooleanFieldItem(booleanValues);
+ }
+
+ BaseDefaultFieldItem decodeReadResponseSignedByteField(S7Field field, ByteBuf data) {
+ Byte[] bytes = readAllValues(Byte.class, field, i -> data.readByte());
+ return new DefaultByteFieldItem(bytes);
+ }
+
+ BaseDefaultFieldItem decodeReadResponseUnsignedByteField(S7Field field, ByteBuf data) {
+ Short[] shorts = readAllValues(Short.class, field, i -> data.readUnsignedByte());
+ return new DefaultShortFieldItem(shorts);
+ }
+
+ BaseDefaultFieldItem decodeReadResponseSignedShortField(S7Field field, ByteBuf data) {
+ Short[] shorts = readAllValues(Short.class, field, i -> data.readShort());
+ return new DefaultShortFieldItem(shorts);
+ }
+
+ BaseDefaultFieldItem decodeReadResponseUnsignedShortField(S7Field field, ByteBuf data) {
+ Integer[] ints = readAllValues(Integer.class, field, i -> data.readUnsignedShort());
+ return new DefaultIntegerFieldItem(ints);
+ }
+
+ BaseDefaultFieldItem decodeReadResponseSignedIntegerField(S7Field field, ByteBuf data) {
+ Integer[] ints = readAllValues(Integer.class, field, i -> data.readInt());
+ return new DefaultIntegerFieldItem(ints);
+ }
+
+ BaseDefaultFieldItem decodeReadResponseUnsignedIntegerField(S7Field field, ByteBuf data) {
+ Long[] longs = readAllValues(Long.class, field, i -> data.readUnsignedInt());
+ return new DefaultLongFieldItem(longs);
+ }
+
+ BaseDefaultFieldItem decodeReadResponseSignedLongField(S7Field field, ByteBuf data) {
+ Long[] longs = readAllValues(Long.class, field, i -> data.readLong());
+ return new DefaultLongFieldItem(longs);
+ }
+
+ BaseDefaultFieldItem decodeReadResponseUnsignedLongField(S7Field field, ByteBuf data) {
+ BigInteger[] bigIntegers = readAllValues(BigInteger.class, field, i -> readUnsigned64BitInteger(data));
+ return new DefaultBigIntegerFieldItem(bigIntegers);
+ }
+
+ BaseDefaultFieldItem decodeReadResponseFloatField(S7Field field, ByteBuf data) {
+ Float[] floats = readAllValues(Float.class, field, i -> data.readFloat());
+ return new DefaultFloatFieldItem(floats);
+ }
+
+ BaseDefaultFieldItem decodeReadResponseDoubleField(S7Field field, ByteBuf data) {
+ Double[] doubles = readAllValues(Double.class, field, i -> data.readDouble());
+ return new DefaultDoubleFieldItem(doubles);
+ }
+
+ BaseDefaultFieldItem decodeReadResponseFixedLengthStringField(int numChars, boolean isUtf16, ByteBuf data) {
+ int numBytes = isUtf16 ? numChars * 2 : numChars;
+ String stringValue = data.readCharSequence(numBytes, StandardCharsets.UTF_8).toString();
+ return new DefaultStringFieldItem(stringValue);
+ }
+
+ BaseDefaultFieldItem decodeReadResponseVarLengthStringField(boolean isUtf16, ByteBuf data) {
+ // Max length ... ignored.
+ data.skipBytes(1);
+
+ //reading out byte and transforming that to an unsigned byte within an integer, otherwise longer strings are failing
+ byte currentLengthByte = data.readByte();
+ int currentLength = currentLengthByte & 0xFF;
+ return decodeReadResponseFixedLengthStringField(currentLength, isUtf16, data);
+ }
+
+ BaseDefaultFieldItem decodeReadResponseDateAndTime(S7Field field,ByteBuf data) {
+ LocalDateTime[] localDateTimes = readAllValues(LocalDateTime.class,field, i -> readDateAndTime(data));
+ return new DefaultLocalDateTimeFieldItem(localDateTimes);
+ }
+
+ BaseDefaultFieldItem decodeReadResponseTimeOfDay(S7Field field,ByteBuf data) {
+ LocalTime[] localTimes = readAllValues(LocalTime.class,field, i -> readTimeOfDay(data));
+ return new DefaultLocalTimeFieldItem(localTimes);
+ }
+
+ BaseDefaultFieldItem decodeReadResponseDate(S7Field field,ByteBuf data) {
+ LocalDate[] localTimes = readAllValues(LocalDate.class,field, i -> readDate(data));
+ return new DefaultLocalDateFieldItem(localTimes);
+ }
+
+ private static <T> T[] readAllValues(Class<T> clazz, S7Field field, Function<Integer, T> extract) {
+ try {
+ return IntStream.rangeClosed(1, field.getNumElements())
+ .mapToObj(extract::apply)
+ .collect(Collectors.toList())
+ .toArray((T[]) Array.newInstance(clazz, 0));
+ } catch (IndexOutOfBoundsException e) {
+ throw new PlcRuntimeException("To few bytes in the buffer to read requested type", e);
+ }
+ }
+
+ private static BigInteger readUnsigned64BitInteger(ByteBuf data) {
+ byte[] bytes = new byte[9];
+ // Set the first byte to 0
+ bytes[0] = 0;
+ // Read the next 8 bytes into the rest.
+ data.readBytes(bytes, 1, 8);
+ return new BigInteger(bytes);
+ }
+
+ LocalDateTime readDateAndTime(ByteBuf data) {
+ //per definition for Date_And_Time only the first 6 bytes are used
+
+ int year=convertByteToBcd(data.readByte());
+ int month=convertByteToBcd(data.readByte());
+ int day=convertByteToBcd(data.readByte());
+ int hour=convertByteToBcd(data.readByte());
+ int minute=convertByteToBcd(data.readByte());
+ int second=convertByteToBcd(data.readByte());
+ //skip the last 2 bytes no information present
+ data.readByte();
+ data.readByte();
+
+ //data-type ranges from 1990 up to 2089
+ if(year>=90){
+ year+=1900;
+ }
+ else{
+ year+=2000;
+ }
+
+ return LocalDateTime.of(year,month,day,hour,minute,second);
+ }
+
+ LocalTime readTimeOfDay(ByteBuf data) {
+ //per definition for Date_And_Time only the first 6 bytes are used
+ int millisSinsMidnight = data.readInt();
+ return LocalTime.now().withHour(0).withMinute(0).withSecond(0).withNano(0).plus(millisSinsMidnight, ChronoUnit.MILLIS);
+ }
+
+ LocalDate readDate(ByteBuf data) {
+ //per definition for Date_And_Time only the first 6 bytes are used
+ int daysSince1990 = data.readUnsignedShort();
+ return LocalDate.now().withYear(1990).withDayOfMonth(1).withMonth(1).plus(daysSince1990, ChronoUnit.DAYS);
+ }
+
+ /**
+ * converts incoming byte to an integer regarding used BCD format
+ * @param incomingByte
+ * @return converted BCD number
+ */
+ private static int convertByteToBcd(byte incomingByte) {
+ int dec = (incomingByte >> 4) * 10;
+ return dec + (incomingByte & 0x0f);
+ }
+
+ /**
+ * Little helper method to parse Siemens article numbers and extract the type of controller.
+ * @param articleNumber article number string.
+ * @return type of controller.
+ */
+ private S7ControllerType lookupControllerType(String articleNumber) {
+ if (!articleNumber.startsWith("6ES7 ")) {
+ return S7ControllerType.ANY;
+ }
+ String model = articleNumber.substring(articleNumber.indexOf(' ') + 1, articleNumber.indexOf(' ') + 2);
+ switch (model) {
+ case "2":
+ return S7ControllerType.S7_1200;
+ case "5":
+ return S7ControllerType.S7_1500;
+ case "3":
+ return S7ControllerType.S7_300;
+ case "4":
+ return S7ControllerType.S7_400;
+ default:
+ if (logger.isInfoEnabled()) {
+ logger.info(String.format("Looking up unknown article number %s", articleNumber));
+ }
+ return S7ControllerType.ANY;
+ }
+ }
+
+ protected S7Address toS7Address(PlcField field) {
+ if(!(field instanceof S7Field)) {
+ throw new RuntimeException("Unsupported address type " + field.getClass().getName());
+ }
+ S7Field s7Field = (S7Field) field;
+ return new S7AddressAny(s7Field.getDataType(), s7Field.getNumElements(), s7Field.getBlockNumber(),
+ s7Field.getMemoryArea(), s7Field.getByteOffset(), s7Field.getBitOffset());
+ }
+
+}
diff --git a/sandbox/test-java-s7-driver/src/main/java/org/apache/plc4x/java/s7/readwrite/protocol/S7Protocol.java b/sandbox/test-java-s7-driver/src/main/java/org/apache/plc4x/java/s7/readwrite/protocol/S7Protocol.java
new file mode 100644
index 0000000..edb34e8
--- /dev/null
+++ b/sandbox/test-java-s7-driver/src/main/java/org/apache/plc4x/java/s7/readwrite/protocol/S7Protocol.java
@@ -0,0 +1,67 @@
+/*
+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.s7.readwrite.protocol;
+
+import io.netty.buffer.ByteBuf;
+import org.apache.plc4x.java.base.GeneratedDriverByteToMessageCodec;
+import org.apache.plc4x.java.s7.readwrite.*;
+import org.apache.plc4x.java.s7.readwrite.io.TPKTPacketIO;
+import org.apache.plc4x.java.utils.MessageIO;
+import org.apache.plc4x.java.utils.ParseException;
+import org.apache.plc4x.java.utils.ReadBuffer;
+import org.apache.plc4x.java.utils.WriteBuffer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+public class S7Protocol extends GeneratedDriverByteToMessageCodec<TPKTPacket> {
+
+ private static final Logger logger = LoggerFactory.getLogger(S7Protocol.class);
+
+ public S7Protocol() {
+ super(new MessageIO<TPKTPacket, TPKTPacket>() {
+ @Override
+ public TPKTPacket parse(ReadBuffer io) throws ParseException {
+ return TPKTPacketIO.parse(io);
+ }
+
+ @Override
+ public void serialize(WriteBuffer io, TPKTPacket value) throws ParseException {
+ TPKTPacketIO.serialize(io, value);
+ }
+ });
+ }
+
+ @Override
+ protected int getPacketSize(ByteBuf byteBuf) {
+ if(byteBuf.readableBytes() >= 4) {
+ return byteBuf.getUnsignedShort(byteBuf.readerIndex() + 2);
+ }
+ return -1;
+ }
+
+ @Override
+ protected void removeRestOfCorruptPackage(ByteBuf byteBuf) {
+ while (byteBuf.getUnsignedByte(0) != TPKTPacket.PROTOCOLID) {
+ // Just consume the bytes till the next possible start position.
+ byteBuf.readByte();
+ }
+ }
+
+}
diff --git a/sandbox/test-java-s7-driver/src/main/java/org/apache/plc4x/java/s7/readwrite/types/S7ControllerType.java b/sandbox/test-java-s7-driver/src/main/java/org/apache/plc4x/java/s7/readwrite/types/S7ControllerType.java
new file mode 100644
index 0000000..f2956fe
--- /dev/null
+++ b/sandbox/test-java-s7-driver/src/main/java/org/apache/plc4x/java/s7/readwrite/types/S7ControllerType.java
@@ -0,0 +1,30 @@
+/*
+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.s7.readwrite.types;
+
+public enum S7ControllerType {
+
+ ANY,
+ S7_300,
+ S7_400,
+ S7_1200,
+ S7_1500,
+ LOGO
+
+}
diff --git a/sandbox/test-java-s7-driver/src/main/java/org/apache/plc4x/java/s7/readwrite/utils/S7Field.java b/sandbox/test-java-s7-driver/src/main/java/org/apache/plc4x/java/s7/readwrite/utils/S7Field.java
new file mode 100644
index 0000000..69b41d5
--- /dev/null
+++ b/sandbox/test-java-s7-driver/src/main/java/org/apache/plc4x/java/s7/readwrite/utils/S7Field.java
@@ -0,0 +1,265 @@
+/*
+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.s7.readwrite.utils;
+
+import org.apache.commons.lang3.NotImplementedException;
+import org.apache.plc4x.java.api.exceptions.PlcInvalidFieldException;
+import org.apache.plc4x.java.api.model.PlcField;
+import org.apache.plc4x.java.s7.readwrite.types.MemoryArea;
+import org.apache.plc4x.java.s7.readwrite.types.TransportSize;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class S7Field implements PlcField {
+
+ //byteOffset theoretically can reach up to 2097151 ... see checkByteOffset() below --> 7digits
+ private static final Pattern ADDRESS_PATTERN =
+ Pattern.compile("^%(?<memoryArea>.)(?<transferSizeCode>[XBWD]?)(?<byteOffset>\\d{1,7})(.(?<bitOffset>[0-7]))?:(?<dataType>[a-zA-Z_]+)(\\[(?<numElements>\\d+)])?");
+
+ //blockNumber usually has its max hat around 64000 --> 5digits
+ private static final Pattern DATA_BLOCK_ADDRESS_PATTERN =
+ Pattern.compile("^%DB(?<blockNumber>\\d{1,5}).DB(?<transferSizeCode>[XBWD]?)(?<byteOffset>\\d{1,7})(.(?<bitOffset>[0-7]))?:(?<dataType>[a-zA-Z_]+)(\\[(?<numElements>\\d+)])?");
+
+ private static final String DATA_TYPE = "dataType";
+ private static final String TRANSFER_SIZE_CODE = "transferSizeCode";
+ private static final String BLOCK_NUMBER = "blockNumber";
+ private static final String BYTE_OFFSET = "byteOffset";
+ private static final String BIT_OFFSET = "bitOffset";
+ private static final String NUM_ELEMENTS = "numElements";
+ private static final String MEMORY_AREA = "memoryArea";
+
+ private final TransportSize dataType;
+ private final MemoryArea memoryArea;
+ private final int blockNumber;
+ private final int byteOffset;
+ private final byte bitOffset;
+ private final int numElements;
+
+ private S7Field(TransportSize dataType, MemoryArea memoryArea, int blockNumber, int byteOffset, byte bitOffset, int numElements) {
+ this.dataType = dataType;
+ this.memoryArea = memoryArea;
+ this.blockNumber = blockNumber;
+ this.byteOffset = byteOffset;
+ this.bitOffset = bitOffset;
+ this.numElements = numElements;
+ }
+
+ public TransportSize getDataType() {
+ return dataType;
+ }
+
+ public MemoryArea getMemoryArea() {
+ return memoryArea;
+ }
+
+ public int getBlockNumber() {
+ return blockNumber;
+ }
+
+ public int getByteOffset() {
+ return byteOffset;
+ }
+
+ public byte getBitOffset() {
+ return bitOffset;
+ }
+
+ public int getNumElements() {
+ return numElements;
+ }
+
+ public static boolean matches(String fieldString) {
+ return DATA_BLOCK_ADDRESS_PATTERN.matcher(fieldString).matches() ||
+ ADDRESS_PATTERN.matcher(fieldString).matches();
+ }
+
+ /**
+ * @return Java type of expected response.
+ *
+ * TODO validate all Methods existing are implemented
+ */
+ @Override
+ public Class<?> getDefaultJavaType() {
+ switch (dataType){
+ case STRING:
+ return String.class;
+ case USINT:
+ case SINT:
+ case UINT:
+ case INT:
+ case DINT:
+ return Integer.class;
+ case UDINT:
+ case ULINT:
+ case LINT:
+ return Long.class;
+ case BOOL:
+ return Boolean.class;
+ case REAL:
+ case LREAL:
+ return Double.class;
+ case DATE_AND_TIME:
+ return LocalDateTime.class;
+ case DATE:
+ return LocalDate.class;
+ case TIME_OF_DAY:
+ return LocalTime.class;
+ default:
+ throw new NotImplementedException("The response type for datatype " + dataType + " is not yet implemented");
+ }
+ }
+
+ public static S7Field of(String fieldString) {
+ Matcher matcher = DATA_BLOCK_ADDRESS_PATTERN.matcher(fieldString);
+ if(matcher.matches()) {
+ TransportSize dataType = TransportSize.valueOf(matcher.group(DATA_TYPE));
+ MemoryArea memoryArea = MemoryArea.DATA_BLOCKS;
+ Short transferSizeCode = getSizeCode(matcher.group(TRANSFER_SIZE_CODE));
+
+ int blockNumber = checkDatablockNumber(Integer.parseInt(matcher.group(BLOCK_NUMBER)));
+
+ int byteOffset = checkByteOffset(Integer.parseInt(matcher.group(BYTE_OFFSET)));
+
+ byte bitOffset = 0;
+ if(matcher.group(BIT_OFFSET) != null) {
+ bitOffset = Byte.parseByte(matcher.group(BIT_OFFSET));
+ } else if(dataType == TransportSize.BOOL) {
+ throw new PlcInvalidFieldException("Expected bit offset for BOOL parameters.");
+ }
+ int numElements = 1;
+ if(matcher.group(NUM_ELEMENTS) != null) {
+ numElements = Integer.parseInt(matcher.group(NUM_ELEMENTS));
+ }
+ numElements = calcNumberOfElementsForIndividualTypes(numElements,dataType);
+ if((transferSizeCode != null) && (dataType.getSizeCode() != transferSizeCode)) {
+ throw new PlcInvalidFieldException("Transfer size code '" + transferSizeCode +
+ "' doesn't match specified data type '" + dataType.name() + "'");
+ }
+ return new S7Field(dataType, memoryArea, blockNumber, byteOffset, bitOffset, numElements);
+ } else {
+ matcher = ADDRESS_PATTERN.matcher(fieldString);
+ if (matcher.matches()) {
+ TransportSize dataType = TransportSize.valueOf(matcher.group(DATA_TYPE));
+ MemoryArea memoryArea = getMemoryAreaForShortName(matcher.group(MEMORY_AREA));
+ Short transferSizeCode = getSizeCode(matcher.group(TRANSFER_SIZE_CODE));
+
+ int byteOffset = checkByteOffset(Integer.parseInt(matcher.group(BYTE_OFFSET)));
+
+ byte bitOffset = 0;
+ if(matcher.group(BIT_OFFSET) != null) {
+ bitOffset = Byte.parseByte(matcher.group(BIT_OFFSET));
+ } else if(dataType == TransportSize.BOOL) {
+ throw new PlcInvalidFieldException("Expected bit offset for BOOL parameters.");
+ }
+ int numElements = 1;
+ if(matcher.group(NUM_ELEMENTS) != null) {
+ numElements = Integer.parseInt(matcher.group(NUM_ELEMENTS));
+ }
+ numElements = calcNumberOfElementsForIndividualTypes(numElements,dataType);
+ if((transferSizeCode != null) && (dataType.getSizeCode() != transferSizeCode)) {
+ throw new PlcInvalidFieldException("Transfer size code '" + transferSizeCode +
+ "' doesn't match specified data type '" + dataType.name() + "'");
+ }
+ return new S7Field(dataType, memoryArea, (short) 0, byteOffset, bitOffset, numElements);
+ }
+ }
+ throw new PlcInvalidFieldException("Unable to parse address: " + fieldString);
+ }
+
+ /**
+ * checks if DatablockNumber of S7Field is in valid range
+ * @param blockNumber given DatablockNumber
+ * @return given blockNumber if Ok, throws PlcInvalidFieldException otherwise
+ */
+ private static int checkDatablockNumber(int blockNumber){
+ //ToDo check the value or add reference - limit eventually depending on active S7 --> make a case selection
+ if(blockNumber>64000 || blockNumber<1){
+ throw new PlcInvalidFieldException("Datablock numbers larger than 64000 or smaller than 1 are not supported.");
+ }
+ return blockNumber;
+ }
+
+ /**
+ * checks if ByteOffset from S7Field is in valid range
+ * @param byteOffset given byteOffset
+ * @return given byteOffset if Ok, throws PlcInvalidFieldException otherwise
+ */
+ private static int checkByteOffset(int byteOffset){
+ //ToDo check the value or add reference
+ if(byteOffset>2097151 || byteOffset<0){
+ throw new PlcInvalidFieldException("ByteOffset must be smaller than 2097151 and positive.");
+ }
+ return byteOffset;
+ }
+
+ /**
+ * correct the storage of "array"-like variables like STRING
+ * @param numElements auto-detected numElements (1 if no numElements in brackets has been given, x if a specific number has been given)
+ * @param dataType detected Transport-Size that represents the data-type
+ * @return corrected numElements if nessesary
+ */
+ private static int calcNumberOfElementsForIndividualTypes(int numElements, TransportSize dataType){
+
+ if(dataType.equals(TransportSize.STRING)){
+ //on valid String-length add two byte because of S7-representation of Strings
+ if(numElements>1 && numElements<=254){
+ return numElements+2;
+ }
+ //connection String usage with "STRING" only --> numElements=1 --> enter default value
+ return 256;
+ }
+ return numElements;
+
+ }
+
+ protected static Short getSizeCode(String value) {
+ if((value == null) || value.isEmpty()) {
+ return null;
+ }
+ if(value.length() > 1) {
+ return null;
+ }
+ return (short) value.getBytes()[0];
+ }
+
+ protected static MemoryArea getMemoryAreaForShortName(String shortName) {
+ for (MemoryArea memoryArea : MemoryArea.values()) {
+ if(memoryArea.getShortName().equals(shortName)) {
+ return memoryArea;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public String toString() {
+ return "S7Field{" +
+ "dataType=" + dataType +
+ ", memoryArea=" + memoryArea +
+ ", blockNumber=" + blockNumber +
+ ", byteOffset=" + byteOffset +
+ ", bitOffset=" + bitOffset +
+ ", numElements=" + numElements +
+ '}';
+ }
+}
diff --git a/sandbox/test-java-s7-driver/src/main/java/org/apache/plc4x/java/s7/readwrite/utils/S7PlcFieldHandler.java b/sandbox/test-java-s7-driver/src/main/java/org/apache/plc4x/java/s7/readwrite/utils/S7PlcFieldHandler.java
new file mode 100644
index 0000000..0e8f658
--- /dev/null
+++ b/sandbox/test-java-s7-driver/src/main/java/org/apache/plc4x/java/s7/readwrite/utils/S7PlcFieldHandler.java
@@ -0,0 +1,569 @@
+/*
+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.s7.readwrite.utils;
+
+import org.apache.plc4x.java.api.exceptions.PlcInvalidFieldException;
+import org.apache.plc4x.java.api.exceptions.PlcRuntimeException;
+import org.apache.plc4x.java.api.model.PlcField;
+import org.apache.plc4x.java.base.connection.DefaultPlcFieldHandler;
+import org.apache.plc4x.java.base.messages.items.*;
+
+import java.lang.reflect.InvocationTargetException;
+import java.math.BigInteger;
+import java.nio.charset.StandardCharsets;
+import java.util.BitSet;
+import java.util.LinkedList;
+import java.util.List;
+
+public class S7PlcFieldHandler extends DefaultPlcFieldHandler {
+
+ @Override
+ public PlcField createField(String fieldQuery) {
+ if (S7Field.matches(fieldQuery)) {
+ return S7Field.of(fieldQuery);
+ }
+ throw new PlcInvalidFieldException(fieldQuery);
+ }
+
+ @Override
+ public BaseDefaultFieldItem encodeBoolean(PlcField field, Object[] values) {
+ S7Field s7Field = (S7Field) field;
+ // All of these types are declared as Bit or Bit-String types.
+ switch (s7Field.getDataType()) {
+ case BOOL:
+ case BYTE:
+ case WORD:
+ case DWORD:
+ case LWORD:
+ return internalEncodeBoolean(field, values);
+ default:
+ throw new PlcRuntimeException("Invalid encoder for type " + s7Field.getDataType().name());
+ }
+ }
+
+ @Override
+ public BaseDefaultFieldItem encodeByte(PlcField field, Object[] values) {
+ S7Field s7Field = (S7Field) field;
+ // All of these types are declared as Bit or Bit-String types.
+ switch (s7Field.getDataType()) {
+ case BYTE:
+ case SINT:
+ case USINT:
+ case CHAR:
+ return internalEncodeInteger(field, values);
+ default:
+ throw new PlcRuntimeException("Invalid encoder for type " + s7Field.getDataType().name());
+ }
+ }
+
+ @Override
+ public BaseDefaultFieldItem encodeShort(PlcField field, Object[] values) {
+ S7Field s7Field = (S7Field) field;
+ switch (s7Field.getDataType()) {
+ case WORD:
+ case INT:
+ case UINT:
+ return internalEncodeInteger(field, values);
+ default:
+ throw new PlcRuntimeException("Invalid encoder for type " + s7Field.getDataType().name());
+ }
+ }
+
+ @Override
+ public BaseDefaultFieldItem encodeInteger(PlcField field, Object[] values) {
+ S7Field s7Field = (S7Field) field;
+ switch (s7Field.getDataType()) {
+ case DWORD:
+ case DINT:
+ case UDINT:
+ return internalEncodeInteger(field, values);
+ default:
+ throw new PlcRuntimeException("Invalid encoder for type " + s7Field.getDataType().name());
+ }
+ }
+
+ @Override
+ public BaseDefaultFieldItem encodeBigInteger(PlcField field, Object[] values) {
+ S7Field s7Field = (S7Field) field;
+ switch (s7Field.getDataType()) {
+ case DWORD:
+ case DINT:
+ case UDINT:
+ return internalEncodeInteger(field, values);
+ default:
+ throw new PlcRuntimeException("Invalid encoder for type " + s7Field.getDataType().name());
+ }
+ }
+
+ @Override
+ public BaseDefaultFieldItem encodeLong(PlcField field, Object[] values) {
+ S7Field s7Field = (S7Field) field;
+ switch (s7Field.getDataType()) {
+ case LWORD:
+ case LINT:
+ case ULINT:
+ return internalEncodeInteger(field, values);
+ default:
+ throw new PlcRuntimeException("Invalid encoder for type " + s7Field.getDataType().name());
+ }
+ }
+
+ @Override
+ public BaseDefaultFieldItem encodeFloat(PlcField field, Object[] values) {
+ S7Field s7Field = (S7Field) field;
+ switch (s7Field.getDataType()) {
+ case REAL:
+ return internalEncodeFloatingPoint(field, values);
+ default:
+ throw new PlcRuntimeException("Invalid encoder for type " + s7Field.getDataType().name());
+ }
+ }
+
+ @Override
+ public BaseDefaultFieldItem encodeDouble(PlcField field, Object[] values) {
+ S7Field s7Field = (S7Field) field;
+ switch (s7Field.getDataType()) {
+ case LREAL:
+ return internalEncodeFloatingPoint(field, values);
+ default:
+ throw new PlcRuntimeException("Invalid encoder for type " + s7Field.getDataType().name());
+ }
+ }
+
+ @Override
+ public BaseDefaultFieldItem encodeString(PlcField field, Object[] values) {
+ S7Field s7Field = (S7Field) field;
+ switch (s7Field.getDataType()) {
+ case CHAR:
+ case WCHAR:
+ case STRING:
+ case WSTRING:
+ return internalEncodeString(field, values);
+ default:
+ throw new PlcRuntimeException("Invalid encoder for type " + s7Field.getDataType().name());
+ }
+ }
+
+ @Override
+ public BaseDefaultFieldItem encodeTime(PlcField field, Object[] values) {
+ S7Field s7Field = (S7Field) field;
+ switch (s7Field.getDataType()) {
+ case TIME:
+ return internalEncodeTemporal(field, values);
+ default:
+ throw new PlcRuntimeException("Invalid encoder for type " + s7Field.getDataType().name());
+ }
+ }
+
+ @Override
+ public BaseDefaultFieldItem encodeDate(PlcField field, Object[] values) {
+ S7Field s7Field = (S7Field) field;
+ switch (s7Field.getDataType()) {
+ case DATE:
+ return internalEncodeTemporal(field, values);
+ default:
+ throw new PlcRuntimeException("Invalid encoder for type " + s7Field.getDataType().name());
+ }
+ }
+
+ @Override
+ public BaseDefaultFieldItem encodeDateTime(PlcField field, Object[] values) {
+ S7Field s7Field = (S7Field) field;
+ switch (s7Field.getDataType()) {
+ case DATE_AND_TIME:
+ return internalEncodeTemporal(field, values);
+ default:
+ throw new PlcRuntimeException("Invalid encoder for type " + s7Field.getDataType().name());
+ }
+ }
+
+ private BaseDefaultFieldItem internalEncodeBoolean(PlcField field, Object[] values) {
+ S7Field s7Field = (S7Field) field;
+ switch (s7Field.getDataType()) {
+ case BOOL:
+ case BYTE:
+ case WORD:
+ case DWORD:
+ case LWORD:
+ break;
+ default:
+ throw new IllegalArgumentException(
+ "Cannot assign boolean values to " + s7Field.getDataType().name() + " fields.");
+ }
+ List<Boolean> booleanValues = new LinkedList<>();
+ for (Object value : values) {
+ if (value instanceof Boolean) {
+ Boolean booleanValue = (Boolean) value;
+ booleanValues.add(booleanValue);
+ } else if (value instanceof Byte) {
+ Byte byteValue = (Byte) value;
+ BitSet bitSet = BitSet.valueOf(new byte[]{byteValue});
+ for (int i = 0; i < 8; i++) {
+ booleanValues.add(bitSet.get(i));
+ }
+ } else if (value instanceof Short) {
+ Short shortValue = (Short) value;
+ BitSet bitSet = BitSet.valueOf(new long[]{shortValue});
+ for (int i = 0; i < 16; i++) {
+ booleanValues.add(bitSet.get(i));
+ }
+ } else if (value instanceof Integer) {
+ Integer integerValue = (Integer) value;
+ BitSet bitSet = BitSet.valueOf(new long[]{integerValue});
+ for (int i = 0; i < 32; i++) {
+ booleanValues.add(bitSet.get(i));
+ }
+ } else if (value instanceof Long) {
+ long longValue = (Long) value;
+ BitSet bitSet = BitSet.valueOf(new long[]{longValue});
+ for (int i = 0; i < 64; i++) {
+ booleanValues.add(bitSet.get(i));
+ }
+ } else {
+ throw new IllegalArgumentException(
+ "Value of type " + value.getClass().getName() +
+ " is not assignable to " + s7Field.getDataType().name() + " fields.");
+ }
+ }
+ return new DefaultBooleanFieldItem(booleanValues.toArray(new Boolean[0]));
+ }
+
+ private BaseDefaultFieldItem internalEncodeInteger(PlcField field, Object[] values) {
+ S7Field s7Field = (S7Field) field;
+
+ // Initialize the constraints.
+ BigInteger minValue;
+ BigInteger maxValue;
+ Class<? extends BaseDefaultFieldItem> fieldType;
+ Class<?> valueType;
+ Object[] castedValues;
+ switch (s7Field.getDataType()) {
+ case BYTE:
+ minValue = BigInteger.valueOf((long) Byte.MIN_VALUE);
+ maxValue = BigInteger.valueOf((long) Byte.MAX_VALUE);
+ fieldType = DefaultByteFieldItem.class;
+ valueType = Byte[].class;
+ castedValues = new Byte[values.length];
+ break;
+ case WORD:
+ minValue = BigInteger.valueOf((long) Short.MIN_VALUE);
+ maxValue = BigInteger.valueOf((long) Short.MAX_VALUE);
+ fieldType = DefaultShortFieldItem.class;
+ valueType = Short[].class;
+ castedValues = new Short[values.length];
+ break;
+ case DWORD:
+ minValue = BigInteger.valueOf((long) Integer.MIN_VALUE);
+ maxValue = BigInteger.valueOf((long) Integer.MAX_VALUE);
+ fieldType = DefaultIntegerFieldItem.class;
+ valueType = Integer[].class;
+ castedValues = new Integer[values.length];
+ break;
+ case LWORD:
+ minValue = BigInteger.valueOf(Long.MIN_VALUE);
+ maxValue = BigInteger.valueOf(Long.MAX_VALUE);
+ fieldType = DefaultLongFieldItem.class;
+ valueType = Long[].class;
+ castedValues = new Long[values.length];
+ break;
+ case SINT:
+ minValue = BigInteger.valueOf((long) Byte.MIN_VALUE);
+ maxValue = BigInteger.valueOf((long) Byte.MAX_VALUE);
+ fieldType = DefaultByteFieldItem.class;
+ valueType = Byte[].class;
+ castedValues = new Byte[values.length];
+ break;
+ case USINT:
+ minValue = BigInteger.valueOf((long) 0);
+ maxValue = BigInteger.valueOf((long) Byte.MAX_VALUE * 2);
+ fieldType = DefaultShortFieldItem.class;
+ valueType = Short[].class;
+ castedValues = new Short[values.length];
+ break;
+ case INT:
+ minValue = BigInteger.valueOf((long) Short.MIN_VALUE);
+ maxValue = BigInteger.valueOf((long) Short.MAX_VALUE);
+ fieldType = DefaultShortFieldItem.class;
+ valueType = Short[].class;
+ castedValues = new Short[values.length];
+ break;
+ case UINT:
+ minValue = BigInteger.valueOf((long) 0);
+ maxValue = BigInteger.valueOf(((long) Short.MAX_VALUE) * 2);
+ fieldType = DefaultIntegerFieldItem.class;
+ valueType = Integer[].class;
+ castedValues = new Integer[values.length];
+ break;
+ case DINT:
+ minValue = BigInteger.valueOf((long) Integer.MIN_VALUE);
+ maxValue = BigInteger.valueOf((long) Integer.MAX_VALUE);
+ fieldType = DefaultIntegerFieldItem.class;
+ valueType = Integer[].class;
+ castedValues = new Integer[values.length];
+ break;
+ case UDINT:
+ minValue = BigInteger.valueOf((long) 0);
+ maxValue = BigInteger.valueOf(((long) Integer.MAX_VALUE) * 2);
+ fieldType = DefaultLongFieldItem.class;
+ valueType = Long[].class;
+ castedValues = new Long[values.length];
+ break;
+ case LINT:
+ minValue = BigInteger.valueOf(Long.MIN_VALUE);
+ maxValue = BigInteger.valueOf(Long.MAX_VALUE);
+ fieldType = DefaultLongFieldItem.class;
+ valueType = Long[].class;
+ castedValues = new Long[values.length];
+ break;
+ case ULINT:
+ minValue = BigInteger.valueOf((long) 0);
+ maxValue = BigInteger.valueOf(Long.MAX_VALUE).multiply(BigInteger.valueOf((long) 2));
+ fieldType = DefaultBigIntegerFieldItem.class;
+ valueType = BigInteger[].class;
+ castedValues = new BigInteger[values.length];
+ break;
+ default:
+ throw new IllegalArgumentException(
+ "Cannot assign integer values to " + s7Field.getDataType().name() + " fields.");
+ }
+
+ // Check the constraints
+ for (int i = 0; i < values.length; i++) {
+ BigInteger value;
+ if (values[i] instanceof BigInteger) {
+ value = (BigInteger) values[i];
+ } else if ((values[i] instanceof Byte) || (values[i] instanceof Short) ||
+ (values[i] instanceof Integer) || (values[i] instanceof Long)) {
+ value = BigInteger.valueOf(((Number) values[i]).longValue());
+ } else {
+ throw new IllegalArgumentException(
+ "Value of type " + values[i].getClass().getName() +
+ " is not assignable to " + s7Field.getDataType().name() + " fields.");
+ }
+ if (minValue.compareTo(value) > 0) {
+ throw new IllegalArgumentException(
+ "Value of " + value.toString() + " exceeds allowed minimum for type "
+ + s7Field.getDataType().name() + " (min " + minValue.toString() + ")");
+ }
+ if (maxValue.compareTo(value) < 0) {
+ throw new IllegalArgumentException(
+ "Value of " + value.toString() + " exceeds allowed maximum for type "
+ + s7Field.getDataType().name() + " (max " + maxValue.toString() + ")");
+ }
+ if (valueType == Byte[].class) {
+ castedValues[i] = value.byteValue();
+ } else if (valueType == Short[].class) {
+ castedValues[i] = value.shortValue();
+ } else if (valueType == Integer[].class) {
+ castedValues[i] = value.intValue();
+ } else if (valueType == Long[].class) {
+ castedValues[i] = value.longValue();
+ } else {
+ castedValues[i] = value;
+ }
+ }
+
+ // Create the field item.
+ try {
+ return fieldType.getDeclaredConstructor(valueType).newInstance(new Object[]{castedValues});
+ } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
+ throw new PlcRuntimeException("Error initializing field class " + fieldType.getSimpleName(), e);
+ }
+ }
+
+ private BaseDefaultFieldItem internalEncodeFloatingPoint(PlcField field, Object[] values) {
+ S7Field s7Field = (S7Field) field;
+
+ // Initialize the constraints.
+ Double minValue;
+ Double maxValue;
+ Class<? extends BaseDefaultFieldItem> fieldType;
+ Class<?> valueType;
+ Object[] castedValues;
+ switch (s7Field.getDataType()) {
+ case REAL:
+ minValue = (double) -Float.MAX_VALUE;
+ maxValue = (double) Float.MAX_VALUE;
+ fieldType = DefaultFloatFieldItem.class;
+ valueType = Float[].class;
+ castedValues = new Float[values.length];
+ break;
+ case LREAL:
+ minValue = -Double.MAX_VALUE;
+ maxValue = Double.MAX_VALUE;
+ fieldType = DefaultDoubleFieldItem.class;
+ valueType = Double[].class;
+ castedValues = new Double[values.length];
+ break;
+ default:
+ throw new IllegalArgumentException(
+ "Cannot assign floating point values to " + s7Field.getDataType().name() + " fields.");
+ }
+
+ // Check the constraints
+ for (int i = 0; i < values.length; i++) {
+ Double value;
+ if (values[i] instanceof Float) {
+ value = ((Float) values[i]).doubleValue();
+ } else if (values[i] instanceof Double) {
+ value = (Double) values[i];
+ } else {
+ throw new IllegalArgumentException(
+ "Value of type " + values[i].getClass().getName() +
+ " is not assignable to " + s7Field.getDataType().name() + " fields.");
+ }
+ if (value < minValue) {
+ throw new IllegalArgumentException(
+ "Value of " + value + " exceeds allowed minimum for type "
+ + s7Field.getDataType().name() + " (min " + minValue.toString() + ")");
+ }
+ if (value > maxValue) {
+ throw new IllegalArgumentException(
+ "Value of " + value + " exceeds allowed maximum for type "
+ + s7Field.getDataType().name() + " (max " + maxValue.toString() + ")");
+ }
+ if (valueType == Float[].class) {
+ castedValues[i] = value.floatValue();
+ } else {
+ castedValues[i] = value;
+ }
+ }
+
+ // Create the field item.
+ try {
+ return fieldType.getDeclaredConstructor(valueType).newInstance(new Object[]{castedValues});
+ } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) {
+ throw new PlcRuntimeException("Error initializing field class " + fieldType.getSimpleName(), e);
+ }
+ }
+
+ private BaseDefaultFieldItem internalEncodeString(PlcField field, Object[] values) {
+ S7Field s7Field = (S7Field) field;
+
+ // Initialize the constraints.
+ int maxLength;
+ boolean encoding16Bit;
+ switch (s7Field.getDataType()) {
+ case CHAR:
+ maxLength = 1;
+ encoding16Bit = false;
+ break;
+ case WCHAR:
+ maxLength = 1;
+ encoding16Bit = true;
+ break;
+ case STRING:
+ maxLength = 254;
+ encoding16Bit = false;
+ break;
+ case WSTRING:
+ maxLength = 254;
+ encoding16Bit = true;
+ break;
+ default:
+ throw new IllegalArgumentException(
+ "Cannot assign string values to " + s7Field.getDataType().name() + " fields.");
+ }
+
+ // Check the constraints and create the strings.
+ List<String> stringValues = new LinkedList<>();
+ for (Object value : values) {
+ if (value instanceof String) {
+ String stringValue = (String) value;
+ if (stringValue.length() > maxLength) {
+ throw new IllegalArgumentException(
+ "String length " + stringValue.length() + " exceeds allowed maximum for type "
+ + s7Field.getDataType().name() + " (max " + maxLength + ")");
+ }
+ stringValues.add(stringValue);
+ }
+ // All other types just translate to max one String character.
+ else if (value instanceof Byte) {
+ Byte byteValue = (Byte) value;
+ byte[] stringBytes = new byte[]{byteValue};
+ if (encoding16Bit) {
+ stringValues.add(new String(stringBytes, StandardCharsets.UTF_16));
+ } else {
+ stringValues.add(new String(stringBytes, StandardCharsets.UTF_8));
+ }
+ } else if (value instanceof Short) {
+ Short shortValue = (Short) value;
+ byte[] stringBytes = new byte[2];
+ stringBytes[0] = (byte) (shortValue >> 8);
+ stringBytes[1] = (byte) (shortValue & 0xFF);
+ if (encoding16Bit) {
+ stringValues.add(new String(stringBytes, StandardCharsets.UTF_16));
+ } else {
+ stringValues.add(new String(stringBytes, StandardCharsets.UTF_8));
+ }
+ } else if (value instanceof Integer) {
+ Integer integerValue = (Integer) value;
+ byte[] stringBytes = new byte[4];
+ stringBytes[0] = (byte) ((integerValue >> 24) & 0xFF);
+ stringBytes[1] = (byte) ((integerValue >> 16) & 0xFF);
+ stringBytes[2] = (byte) ((integerValue >> 8) & 0xFF);
+ stringBytes[3] = (byte) (integerValue & 0xFF);
+ if (encoding16Bit) {
+ stringValues.add(new String(stringBytes, StandardCharsets.UTF_16));
+ } else {
+ stringValues.add(new String(stringBytes, StandardCharsets.UTF_8));
+ }
+ } else if (value instanceof Long) {
+ Long longValue = (Long) value;
+ byte[] stringBytes = new byte[8];
+ stringBytes[0] = (byte) ((longValue >> 56) & 0xFF);
+ stringBytes[1] = (byte) ((longValue >> 48) & 0xFF);
+ stringBytes[2] = (byte) ((longValue >> 40) & 0xFF);
+ stringBytes[3] = (byte) ((longValue >> 32) & 0xFF);
+ stringBytes[4] = (byte) ((longValue >> 24) & 0xFF);
+ stringBytes[5] = (byte) ((longValue >> 16) & 0xFF);
+ stringBytes[6] = (byte) ((longValue >> 8) & 0xFF);
+ stringBytes[7] = (byte) (longValue & 0xFF);
+ if (encoding16Bit) {
+ stringValues.add(new String(stringBytes, StandardCharsets.UTF_16));
+ } else {
+ stringValues.add(new String(stringBytes, StandardCharsets.UTF_8));
+ }
+ } else {
+ throw new IllegalArgumentException(
+ "Value of type " + value.getClass().getName() +
+ " is not assignable to " + s7Field.getDataType().name() + " fields.");
+ }
+ }
+
+ // Create the field item.
+ return new DefaultStringFieldItem(stringValues.toArray(new String[0]));
+ }
+
+ private BaseDefaultFieldItem internalEncodeTemporal(PlcField field, Object[] values) {
+ S7Field s7Field = (S7Field) field;
+ switch (s7Field.getDataType()) {
+ case TIME:
+ // TODO: I think I should implement this some time ...
+ case DATE:
+ // TODO: I think I should implement this some time ...
+ case DATE_AND_TIME:
+ return new DefaultLocalDateTimeFieldItem();
+ default:
+ throw new IllegalArgumentException(
+ "Cannot assign temporal values to " + s7Field.getDataType().name() + " fields.");
+ }
+ }
+
+}
diff --git a/sandbox/test-java-s7-driver/src/main/java/org/apache/plc4x/java/s7/readwrite/utils/S7TsapIdEncoder.java b/sandbox/test-java-s7-driver/src/main/java/org/apache/plc4x/java/s7/readwrite/utils/S7TsapIdEncoder.java
new file mode 100644
index 0000000..edd8b3c
--- /dev/null
+++ b/sandbox/test-java-s7-driver/src/main/java/org/apache/plc4x/java/s7/readwrite/utils/S7TsapIdEncoder.java
@@ -0,0 +1,48 @@
+/*
+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.s7.readwrite.utils;
+
+import org.apache.plc4x.java.s7.readwrite.types.DeviceGroup;
+
+public class S7TsapIdEncoder {
+
+ private S7TsapIdEncoder() {
+ // Prevent this from being instantiated.
+ }
+
+ public static short encodeS7TsapId(DeviceGroup deviceGroup, int rack, int slot) {
+ short firstByte = (short) (deviceGroup.getValue() << 8);
+ short secondByte = (short) ((rack << 4) | (slot & 0x0F));
+ return (short) (firstByte | secondByte);
+ }
+
+ public static DeviceGroup decodeDeviceGroup(short tsapId) {
+ byte deviceGroupCode = (byte) ((tsapId >> 8) & (0xFF));
+ return DeviceGroup.valueOf(deviceGroupCode);
+ }
+
+ public static int decodeRack(short tsapId) {
+ return (tsapId >> 4) & 0xF;
+ }
+
+ public static int decodeSlot(short tsapId) {
+ return tsapId & 0xF;
+ }
+
+}
diff --git a/sandbox/test-java-s7-driver/src/main/resources/META-INF/services/org.apache.plc4x.java.spi.PlcDriver b/sandbox/test-java-s7-driver/src/main/resources/META-INF/services/org.apache.plc4x.java.spi.PlcDriver
new file mode 100644
index 0000000..728a544
--- /dev/null
+++ b/sandbox/test-java-s7-driver/src/main/resources/META-INF/services/org.apache.plc4x.java.spi.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.s7.readwrite.S7Driver