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 2022/09/27 19:48:20 UTC

[plc4x] branch develop updated: chore(plc4j/ads): Finished implementing single and multi-item requests containing purely simple data types (no lists and no structs)

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

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


The following commit(s) were added to refs/heads/develop by this push:
     new 4fb452aee chore(plc4j/ads): Finished implementing single and multi-item requests containing purely simple data types (no lists and no structs)
4fb452aee is described below

commit 4fb452aeec7b963614d056b7240f3d65522edbde
Author: Christofer Dutz <ch...@c-ware.de>
AuthorDate: Tue Sep 27 21:45:31 2022 +0200

    chore(plc4j/ads): Finished implementing single and multi-item requests containing purely simple data types (no lists and no structs)
---
 .../templates/java/data-io-template.java.ftlh      |   2 +
 .../plc4x/java/ads/field/DirectAdsField.java       |   2 +-
 .../plc4x/java/ads/protocol/AdsProtocolLogic.java  | 169 ++++++++++++++-------
 .../plc4x/protocol/ads/ManualAdsDriverTest.java    |  57 ++++---
 .../java/s7/readwrite/ManualS7DriverTest.java      |  50 +++---
 .../java/spi/generation/WriteBufferByteBased.java  |  21 ++-
 .../org/apache/plc4x/java/spi/values/PlcDATE.java  |  13 ++
 .../plc4x/java/spi/values/PlcDATE_AND_TIME.java    |  13 +-
 .../org/apache/plc4x/java/spi/values/PlcLTIME.java |  30 ++++
 .../apache/plc4x/java/spi/values/PlcSTRING.java    |   2 +-
 .../org/apache/plc4x/java/spi/values/PlcTIME.java  |  21 +++
 .../plc4x/java/spi/values/PlcTIME_OF_DAY.java      |  12 ++
 .../apache/plc4x/java/spi/values/PlcValues.java    |   5 +-
 .../spi/values/{PlcSTRING.java => PlcWSTRING.java} |  16 +-
 .../org/apache/plc4x/test/manual/ManualTest.java   |  71 +++++++--
 15 files changed, 357 insertions(+), 127 deletions(-)

diff --git a/code-generation/language-java/src/main/resources/templates/java/data-io-template.java.ftlh b/code-generation/language-java/src/main/resources/templates/java/data-io-template.java.ftlh
index efeaa6625..4c795c326 100644
--- a/code-generation/language-java/src/main/resources/templates/java/data-io-template.java.ftlh
+++ b/code-generation/language-java/src/main/resources/templates/java/data-io-template.java.ftlh
@@ -422,6 +422,8 @@ public class ${type.name} {
             ${helper.getLanguageTypeNameForField(simpleField)} ${simpleField.name} = (${helper.getLanguageTypeNameForField(field)}) _value.getStruct().get("${simpleField.name}").get${helper.getLanguageTypeNameForField(simpleField)?cap_first}();
                         <#else>
                             <#if simpleField.name == "value">
+            ${helper.getLanguageTypeNameForField(simpleField)} ${simpleField.name} = (${helper.getLanguageTypeNameForField(field)}) _value.get${helper.getLanguageTypeNameForField(simpleField)?cap_first}();
+                            <#elseif simpleField.name == "secondsSinceEpoch">
             ${helper.getLanguageTypeNameForField(simpleField)} ${simpleField.name} = (${helper.getLanguageTypeNameForField(field)}) _value.get${helper.getLanguageTypeNameForField(simpleField)?cap_first}();
                             <#else>
                                 <#-- Just for now -->
diff --git a/plc4j/drivers/ads/src/main/java/org/apache/plc4x/java/ads/field/DirectAdsField.java b/plc4j/drivers/ads/src/main/java/org/apache/plc4x/java/ads/field/DirectAdsField.java
index cb6f79793..7a689aeb2 100644
--- a/plc4j/drivers/ads/src/main/java/org/apache/plc4x/java/ads/field/DirectAdsField.java
+++ b/plc4j/drivers/ads/src/main/java/org/apache/plc4x/java/ads/field/DirectAdsField.java
@@ -105,7 +105,7 @@ public class DirectAdsField implements AdsField {
         return indexOffset;
     }
 
-    public String getAdsDataTypeName() {
+    public String getPlcDataType() {
         return adsDataTypeName;
     }
 
diff --git a/plc4j/drivers/ads/src/main/java/org/apache/plc4x/java/ads/protocol/AdsProtocolLogic.java b/plc4j/drivers/ads/src/main/java/org/apache/plc4x/java/ads/protocol/AdsProtocolLogic.java
index 1f4fa085f..077787239 100644
--- a/plc4j/drivers/ads/src/main/java/org/apache/plc4x/java/ads/protocol/AdsProtocolLogic.java
+++ b/plc4j/drivers/ads/src/main/java/org/apache/plc4x/java/ads/protocol/AdsProtocolLogic.java
@@ -616,7 +616,7 @@ public class AdsProtocolLogic extends Plc4xProtocolBase<AmsTCPPacket> implements
     protected CompletableFuture<PlcReadResponse> singleRead(PlcReadRequest readRequest, DirectAdsField directAdsField) {
         CompletableFuture<PlcReadResponse> future = new CompletableFuture<>();
 
-        String dataTypeName = directAdsField.getAdsDataTypeName();
+        String dataTypeName = directAdsField.getPlcDataType();
         AdsDataTypeTableEntry adsDataTypeTableEntry = dataTypeTable.get(dataTypeName);
         long size = adsDataTypeTableEntry.getSize();
 
@@ -655,7 +655,7 @@ public class AdsProtocolLogic extends Plc4xProtocolBase<AmsTCPPacket> implements
         // Calculate the expected size of the response data.
         long expectedResponseDataSize = resolvedFields.values().stream().mapToLong(
             field -> {
-                String dataTypeName = field.getAdsDataTypeName();
+                String dataTypeName = field.getPlcDataType();
                 AdsDataTypeTableEntry adsDataTypeTableEntry = dataTypeTable.get(dataTypeName);
                 long size = adsDataTypeTableEntry.getSize();
                 // Status code + payload size
@@ -669,7 +669,7 @@ public class AdsProtocolLogic extends Plc4xProtocolBase<AmsTCPPacket> implements
             expectedResponseDataSize, readRequest.getFieldNames().stream().map(fieldName -> {
                 AdsField field = (AdsField) readRequest.getField(fieldName);
                 DirectAdsField directAdsField = resolvedFields.get(field);
-                String dataTypeName = directAdsField.getAdsDataTypeName();
+                String dataTypeName = directAdsField.getPlcDataType();
                 AdsDataTypeTableEntry adsDataTypeTableEntry = dataTypeTable.get(dataTypeName);
                 long size = adsDataTypeTableEntry.getSize();
                 return new AdsMultiRequestItemRead(
@@ -762,7 +762,7 @@ public class AdsProtocolLogic extends Plc4xProtocolBase<AmsTCPPacket> implements
 
     private ResponseItem<PlcValue> parseResponseItem(DirectAdsField field, ReadBuffer readBuffer) {
         try {
-            String dataTypeName = field.getAdsDataTypeName();
+            String dataTypeName = field.getPlcDataType();
             AdsDataTypeTableEntry adsDataTypeTableEntry = dataTypeTable.get(dataTypeName);
             PlcValueType plcValueType = getPlcValueTypeForAdsDataType(adsDataTypeTableEntry);
 
@@ -780,14 +780,14 @@ public class AdsProtocolLogic extends Plc4xProtocolBase<AmsTCPPacket> implements
                     try {
                         return parsePlcValue(plcValueType, adsDataTypeTableEntry, stringLength, readBuffer);
                     } catch (ParseException e) {
-                        LOGGER.warn("Error parsing field item of type: '{}' (at position {}})", field.getAdsDataTypeName(), i, e);
+                        LOGGER.warn("Error parsing field item of type: '{}' (at position {}})", field.getPlcDataType(), i, e);
                     }
                     return null;
                 }).toArray(PlcValue[]::new);
                 return new ResponseItem<>(PlcResponseCode.OK, IEC61131ValueHandler.of(resultItems));
             }
         } catch (Exception e) {
-            LOGGER.warn(String.format("Error parsing field item of type: '%s'", field.getAdsDataTypeName()), e);
+            LOGGER.warn(String.format("Error parsing field item of type: '%s'", field.getPlcDataType()), e);
             return new ResponseItem<>(PlcResponseCode.INTERNAL_ERROR, null);
         }
     }
@@ -909,26 +909,14 @@ public class AdsProtocolLogic extends Plc4xProtocolBase<AmsTCPPacket> implements
     protected CompletableFuture<PlcWriteResponse> singleWrite(PlcWriteRequest writeRequest, DirectAdsField directAdsField) {
         CompletableFuture<PlcWriteResponse> future = new CompletableFuture<>();
 
-/*        final String fieldName = writeRequest.getFieldNames().iterator().next();
-        final AdsField plcField = (AdsField) writeRequest.getField(fieldName);
+        final String fieldName = writeRequest.getFieldNames().iterator().next();
         final PlcValue plcValue = writeRequest.getPlcValue(fieldName);
-        final int stringLength;
-        if (directAdsField.getAdsDataType() == AdsDataType.STRING) {
-            stringLength = plcValue.getString().length() + 1;
-        } else {
-            if (directAdsField.getAdsDataType() == AdsDataType.WSTRING) {
-                stringLength = (plcValue.getString().length() + 1) * 2;
-            } else {
-                stringLength = 0;
-            }
-        }
+
         try {
-            WriteBufferByteBased writeBuffer = new WriteBufferByteBased(DataItem.getLengthInBytes(plcValue,
-                plcField.getAdsDataType().getPlcValueType(), stringLength));
-            DataItem.staticSerialize(writeBuffer, plcValue, plcField.getAdsDataType().getPlcValueType(), stringLength, ByteOrder.LITTLE_ENDIAN);
+            byte[] serializedValue = serializePlcValue(plcValue, directAdsField.getPlcDataType());
             AmsPacket amsPacket = new AdsWriteRequest(configuration.getTargetAmsNetId(), configuration.getTargetAmsPort(),
                 configuration.getSourceAmsNetId(), configuration.getSourceAmsPort(),
-                0, getInvokeId(), directAdsField.getIndexGroup(), directAdsField.getIndexOffset(), writeBuffer.getData());
+                0, getInvokeId(), directAdsField.getIndexGroup(), directAdsField.getIndexOffset(), serializedValue);
             AmsTCPPacket amsTCPPacket = new AmsTCPPacket(amsPacket);
 
             // Start a new request-transaction (Is ended in the response-handler)
@@ -953,53 +941,56 @@ public class AdsProtocolLogic extends Plc4xProtocolBase<AmsTCPPacket> implements
                 }));
         } catch (Exception e) {
             future.completeExceptionally(new PlcException("Error"));
-        }*/
+        }
         return future;
     }
 
     protected CompletableFuture<PlcWriteResponse> multiWrite(PlcWriteRequest writeRequest, Map<AdsField, DirectAdsField> resolvedFields) {
         CompletableFuture<PlcWriteResponse> future = new CompletableFuture<>();
 
-        // Calculate the size of all fields together.
-        // Calculate the expected size of the response data.
-/*        int expectedRequestDataSize = directAdsFields.stream().mapToInt(
-            field -> field.getAdsDataType().getNumBytes() * field.getNumberOfElements()).sum();
-        byte[] writeBuffer = new byte[expectedRequestDataSize];
-        int pos = 0;
+        int numFields = writeRequest.getFields().size();
+        // Serialize all fields.
+        List<byte[]> serializedFields = new ArrayList<>(numFields);
+        Map<DirectAdsField, AdsDataTypeTableEntry> directAdsFields = new LinkedHashMap<>(numFields);
         for (String fieldName : writeRequest.getFieldNames()) {
             final AdsField field = (AdsField) writeRequest.getField(fieldName);
+            final DirectAdsField directAdsField = resolvedFields.get(field);
             final PlcValue plcValue = writeRequest.getPlcValue(fieldName);
-            final int stringLength;
-            if (field.getAdsDataType() == AdsDataType.STRING) {
-                stringLength = plcValue.getString().length() + 1;
-            } else {
-                if (field.getAdsDataType() == AdsDataType.WSTRING) {
-                    stringLength = (plcValue.getString().length() + 1) * 2;
-                } else {
-                    stringLength = 0;
-                }
-            }
+            final AdsDataTypeTableEntry dataType = dataTypeTable.get(directAdsField.getPlcDataType());
             try {
-                WriteBufferByteBased itemWriteBuffer = new WriteBufferByteBased(DataItem.getLengthInBytes(plcValue,
-                    field.getAdsDataType().getPlcValueType(), stringLength));
-                DataItem.staticSerialize(itemWriteBuffer, plcValue,
-                    field.getAdsDataType().getPlcValueType(), stringLength, ByteOrder.LITTLE_ENDIAN);
-                int numBytes = itemWriteBuffer.getPos();
-                System.arraycopy(itemWriteBuffer.getData(), 0, writeBuffer, pos, numBytes);
-                pos += numBytes;
+                byte[] serializedValue = serializePlcValue(plcValue, directAdsField.getPlcDataType());
+                serializedFields.add(serializedValue);
+                directAdsFields.put(directAdsField, dataType);
             } catch (Exception e) {
-                throw new PlcRuntimeException("Error serializing data", e);
+                future.completeExceptionally(new PlcException("Error serializing data", e));
+                return future;
+            }
+        }
+
+        // Calculate the size of all serialized fields together.
+        int serializedSize = serializedFields.stream().mapToInt(
+            serializedField -> serializedField.length).sum();
+
+        // Copy all serialized fields into one buffer.
+        WriteBufferByteBased writeBuffer = new WriteBufferByteBased(serializedSize);
+        for (byte[] serializedField : serializedFields) {
+            try {
+                writeBuffer.writeByteArray("", serializedField);
+            } catch (SerializationException e) {
+                future.completeExceptionally(new PlcException("Error serializing data", e));
+                return future;
             }
         }
 
         // With multi-requests, the index-group is fixed and the index offset indicates the number of elements.
         AmsPacket amsPacket = new AdsReadWriteRequest(configuration.getTargetAmsNetId(), configuration.getTargetAmsPort(),
             configuration.getSourceAmsNetId(), configuration.getSourceAmsPort(),
-            0, getInvokeId(), ReservedIndexGroups.ADSIGRP_MULTIPLE_WRITE.getValue(), directAdsFields.size(), (long) directAdsFields.size() * 4,
-            directAdsFields.stream().map(directAdsField -> new AdsMultiRequestItemWrite(
-                    directAdsField.getIndexGroup(), directAdsField.getIndexOffset(),
-                    ((long) directAdsField.getAdsDataType().getNumBytes() * directAdsField.getNumberOfElements())))
-                .collect(Collectors.toList()), writeBuffer);
+            0, getInvokeId(), ReservedIndexGroups.ADSIGRP_MULTIPLE_WRITE.getValue(), serializedSize,
+            (long) numFields * 4,
+            directAdsFields.entrySet().stream().map(entry -> new AdsMultiRequestItemWrite(
+                    entry.getKey().getIndexGroup(), entry.getKey().getIndexOffset(),
+                    entry.getValue().getEntryLength()))
+                .collect(Collectors.toList()), writeBuffer.getBytes());
         AmsTCPPacket amsTCPPacket = new AmsTCPPacket(amsPacket);
 
         // Start a new request-transaction (Is ended in the response-handler)
@@ -1021,10 +1012,78 @@ public class AdsProtocolLogic extends Plc4xProtocolBase<AmsTCPPacket> implements
                 }
                 // Finish the request-transaction.
                 transaction.endRequest();
-            }));*/
+            }));
         return future;
     }
 
+    protected byte[] serializePlcValue(PlcValue plcValue, String datatypeName) throws SerializationException {
+        // First check, if we have type information available.
+        if (!dataTypeTable.containsKey(datatypeName)) {
+            throw new SerializationException("Could not find data type: " + datatypeName);
+        }
+
+        // Get the data type, allocate enough memory and serialize the value based on the
+        // structure defined by the data type.
+        AdsDataTypeTableEntry dataType = dataTypeTable.get(datatypeName);
+        WriteBufferByteBased writeBuffer = new WriteBufferByteBased((int) dataType.getSize());
+        List<AdsDataTypeArrayInfo> arrayInfo = dataType.getArrayInfo();
+        serializeInternal(plcValue, dataType, arrayInfo, writeBuffer);
+        return writeBuffer.getBytes();
+    }
+
+    protected void serializeInternal(PlcValue contextValue,
+                                     AdsDataTypeTableEntry dataType,
+                                     List<AdsDataTypeArrayInfo> arrayInfo,
+                                     WriteBufferByteBased writeBuffer) throws SerializationException {
+
+        // An array type: Recursively iterate over the elements
+        if (arrayInfo.size() > 0) {
+            if (!contextValue.isList()) {
+                throw new SerializationException("Expected a PlcList, but got a " + contextValue.getPlcValueType().name());
+            }
+            AdsDataTypeArrayInfo curArrayLevel = arrayInfo.get(0);
+            List<? extends PlcValue> list = contextValue.getList();
+            if(curArrayLevel.getNumElements() != list.size()) {
+                throw new SerializationException(String.format(
+                    "Expected a PlcList of size %d, but got one of size %d", curArrayLevel.getNumElements(), list.size()));
+            }
+            for (PlcValue plcValue : list) {
+                serializeInternal(plcValue, dataType, arrayInfo.subList(1, arrayInfo.size()), writeBuffer);
+            }
+        }
+
+        // A complex type
+        else if (dataType.getChildren().size() > 0) {
+            if (!contextValue.isStruct()) {
+                throw new SerializationException("Expected a PlcStruct, but got a " + contextValue.getPlcValueType().name());
+            }
+            PlcStruct plcStruct = (PlcStruct) contextValue;
+            for (AdsDataTypeTableChildEntry child : dataType.getChildren()) {
+                AdsDataTypeTableEntry childDataType = dataTypeTable.get(child.getDataTypeName());
+                if (!plcStruct.hasKey(child.getPropertyName())) {
+                    throw new SerializationException("PlcStruct is missing a child with the name " + child.getPropertyName());
+                }
+                PlcValue childValue = plcStruct.getValue(child.getPropertyName());
+                serializeInternal(childValue, childDataType, childDataType.getArrayInfo(), writeBuffer);
+            }
+        }
+
+        // A simple type
+        else {
+            PlcValueType plcValueType = getPlcValueTypeForAdsDataType(dataType);
+            if (plcValueType == null) {
+                throw new SerializationException("Unsupported simple type: " + dataType.getDataTypeName());
+            }
+            int stringLength = 0;
+            if ((plcValueType == PlcValueType.STRING) || (plcValueType == PlcValueType.WSTRING)) {
+                String stringTypeName = dataType.getDataTypeName();
+                stringLength = Integer.parseInt(
+                    stringTypeName.substring(stringTypeName.indexOf("(") + 1, stringTypeName.indexOf(")")));
+            }
+            DataItem.staticSerialize(writeBuffer, contextValue, plcValueType, stringLength);
+        }
+    }
+
     protected PlcWriteResponse convertToPlc4xWriteResponse(PlcWriteRequest writeRequest, AmsPacket adsData) {
         Map<String, PlcResponseCode> responseCodes = new HashMap<>();
         if (adsData instanceof AdsWriteResponse) {
@@ -1105,7 +1164,7 @@ public class AdsProtocolLogic extends Plc4xProtocolBase<AmsTCPPacket> implements
         List<AmsTCPPacket> amsTCPPackets = subscribeRequest.getFields().stream()
             .map(field -> (DefaultPlcSubscriptionField) field)
             .map(field -> {
-                AdsDataTypeTableEntry adsDataTypeTableEntry = dataTypeTable.get(resolvedFields.get((AdsField) field.getPlcField()).getAdsDataTypeName());
+                AdsDataTypeTableEntry adsDataTypeTableEntry = dataTypeTable.get(resolvedFields.get((AdsField) field.getPlcField()).getPlcDataType());
                 DirectAdsField directAdsField = getDirectAdsFieldForSymbolicName(field.getPlcField());
                 return new AmsTCPPacket(new AdsAddDeviceNotificationRequest(configuration.getTargetAmsNetId(), configuration.getTargetAmsPort(),
                     configuration.getSourceAmsNetId(), configuration.getSourceAmsPort(),
@@ -1154,7 +1213,7 @@ public class AdsProtocolLogic extends Plc4xProtocolBase<AmsTCPPacket> implements
                 .handle(response -> {
                     if (response.getResult() == ReturnCode.OK) {
                         DefaultPlcSubscriptionField subscriptionField = (DefaultPlcSubscriptionField) subscriptionRequest.getField(fieldName);
-                        AdsDataTypeTableEntry adsDataTypeTableEntry = dataTypeTable.get((resolvedFields.get((AdsField) subscriptionField.getPlcField())).getAdsDataTypeName());
+                        AdsDataTypeTableEntry adsDataTypeTableEntry = dataTypeTable.get((resolvedFields.get((AdsField) subscriptionField.getPlcField())).getPlcDataType());
 
                         // Collect notification handle from individual response.
                         responses.put(fieldName, new ResponseItem<>(
diff --git a/plc4j/drivers/ads/src/test/java/org/apache/plc4x/protocol/ads/ManualAdsDriverTest.java b/plc4j/drivers/ads/src/test/java/org/apache/plc4x/protocol/ads/ManualAdsDriverTest.java
index d89a68d95..3beb89099 100644
--- a/plc4j/drivers/ads/src/test/java/org/apache/plc4x/protocol/ads/ManualAdsDriverTest.java
+++ b/plc4j/drivers/ads/src/test/java/org/apache/plc4x/protocol/ads/ManualAdsDriverTest.java
@@ -18,9 +18,13 @@
  */
 package org.apache.plc4x.protocol.ads;
 
+import org.apache.plc4x.java.spi.values.*;
 import org.apache.plc4x.test.manual.ManualTest;
 
-import java.util.Arrays;
+import java.time.Duration;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
 
 public class ManualAdsDriverTest extends ManualTest {
 
@@ -64,7 +68,7 @@ public class ManualAdsDriverTest extends ManualTest {
      */
 
     public ManualAdsDriverTest(String connectionString) {
-        super(connectionString);
+        super(connectionString, true);
     }
 
     public static void main(String[] args) throws Exception {
@@ -76,29 +80,32 @@ public class ManualAdsDriverTest extends ManualTest {
         int targetAmsPort = 851;
         String connectionString = String.format("ads:tcp://%s?sourceAmsNetId=%s&sourceAmsPort=%d&targetAmsNetId=%s&targetAmsPort=%d", spsIp, sourceAmsNetId, sourceAmsPort, targetAmsNetId, targetAmsPort);
         ManualAdsDriverTest test = new ManualAdsDriverTest(connectionString);
-        test.addTestCase("MAIN.hurz_BOOL", true);
-        test.addTestCase("MAIN.hurz_BYTE", Arrays.asList(false, false, true, false, true, false, true, false));
-        test.addTestCase("MAIN.hurz_WORD", Arrays.asList(true, false, true, false, false, true, false, true, true, false, true, true, true, false, false, false));
-        test.addTestCase("MAIN.hurz_DWORD", Arrays.asList(true, true, true, true, true, true, false, false, true, true, false, true, true, true, true, false, true, false, false, false, true, false, false, false, true, false, true, true, true, false, false, false));
-        test.addTestCase("MAIN.hurz_SINT", -42);
-        test.addTestCase("MAIN.hurz_USINT", 42);
-        test.addTestCase("MAIN.hurz_INT", -2424);
-        test.addTestCase("MAIN.hurz_UINT", 42424);
-        test.addTestCase("MAIN.hurz_DINT", -242442424);
-        test.addTestCase("MAIN.hurz_UDINT", 4242442424L);
-        test.addTestCase("MAIN.hurz_LINT", -4242442424242424242L);
-        test.addTestCase("MAIN.hurz_ULINT", 4242442424242424242L);
-        test.addTestCase("MAIN.hurz_REAL", 3.14159265359F);
-        test.addTestCase("MAIN.hurz_LREAL", 2.71828182846D);
-        test.addTestCase("MAIN.hurz_STRING", "hurz");
-        test.addTestCase("MAIN.hurz_WSTRING", "wolf");
-        test.addTestCase("MAIN.hurz_TIME", "PT1.234S");
-        test.addTestCase("MAIN.hurz_LTIME", "PT24015H23M12.034002044S");
-        test.addTestCase("MAIN.hurz_DATE", "1978-03-28");
-        test.addTestCase("MAIN.hurz_TIME_OF_DAY", "15:36:30.123");
-        test.addTestCase("MAIN.hurz_TOD", "16:17:18.123");
-        test.addTestCase("MAIN.hurz_DATE_AND_TIME", "1996-05-06T15:36:30");
-        test.addTestCase("MAIN.hurz_DT", "1972-03-29T00:00");
+        test.addTestCase("MAIN.hurz_BOOL", new PlcBOOL(true));
+        test.addTestCase("MAIN.hurz_BYTE", new PlcBitString(new boolean[]{false, false, true, false, true, false, true, false}));
+        test.addTestCase("MAIN.hurz_WORD", new PlcBitString(new boolean[]{true, false, true, false, false, true, false, true, true, false, true, true, true, false, false, false}));
+        test.addTestCase("MAIN.hurz_DWORD", new PlcBitString(new boolean[]{true, true, true, true, true, true, false, false, true, true, false, true, true, true, true, false, true, false, false, false, true, false, false, false, true, false, true, true, true, false, false, false}));
+        // These are the values, if we decide to go with numeric values instead of bit-strings.
+        //test.addTestCase("MAIN.hurz_BYTE", new PlcBYTE(42));
+        //test.addTestCase("MAIN.hurz_WORD", new PlcWORD(42424));
+        //test.addTestCase("MAIN.hurz_DWORD", new PlcDWORD(4242442424L));
+        test.addTestCase("MAIN.hurz_SINT", new PlcSINT(-42));
+        test.addTestCase("MAIN.hurz_USINT", new PlcUSINT(42));
+        test.addTestCase("MAIN.hurz_INT", new PlcINT(-2424));
+        test.addTestCase("MAIN.hurz_UINT", new PlcUINT(42424));
+        test.addTestCase("MAIN.hurz_DINT", new PlcDINT(-242442424));
+        test.addTestCase("MAIN.hurz_UDINT", new PlcUDINT(4242442424L));
+        test.addTestCase("MAIN.hurz_LINT", new PlcLINT(-4242442424242424242L));
+        test.addTestCase("MAIN.hurz_ULINT", new PlcULINT(4242442424242424242L));
+        test.addTestCase("MAIN.hurz_REAL", new PlcREAL(3.14159265359F));
+        test.addTestCase("MAIN.hurz_LREAL", new PlcLREAL(2.71828182846D));
+        test.addTestCase("MAIN.hurz_STRING", new PlcSTRING("hurz"));
+        test.addTestCase("MAIN.hurz_WSTRING", new PlcWSTRING("wolf"));
+        test.addTestCase("MAIN.hurz_TIME", new PlcTIME(Duration.parse("PT1.234S")));
+        test.addTestCase("MAIN.hurz_LTIME", new PlcLTIME(Duration.parse("PT24015H23M12.034002044S")));
+        test.addTestCase("MAIN.hurz_DATE", new PlcDATE(LocalDate.parse("1978-03-28")));
+        test.addTestCase("MAIN.hurz_TIME_OF_DAY", new PlcTIME_OF_DAY(LocalTime.parse("15:36:30.123")));
+        test.addTestCase("MAIN.hurz_DATE_AND_TIME", new PlcDATE_AND_TIME(LocalDateTime.parse("1996-05-06T15:36:30")));
+        //test.addTestCase("MAIN.hurz_DT", new PlcDT("1972-03-29T00:00"));
         test.run();
     }
 
diff --git a/plc4j/drivers/s7/src/test/java/org/apache/plc4x/java/s7/readwrite/ManualS7DriverTest.java b/plc4j/drivers/s7/src/test/java/org/apache/plc4x/java/s7/readwrite/ManualS7DriverTest.java
index 8a82896c4..4f1488a5b 100644
--- a/plc4j/drivers/s7/src/test/java/org/apache/plc4x/java/s7/readwrite/ManualS7DriverTest.java
+++ b/plc4j/drivers/s7/src/test/java/org/apache/plc4x/java/s7/readwrite/ManualS7DriverTest.java
@@ -18,8 +18,11 @@
  */
 package org.apache.plc4x.java.s7.readwrite;
 
+import org.apache.plc4x.java.spi.values.*;
 import org.apache.plc4x.test.manual.ManualTest;
 
+import java.time.LocalDate;
+import java.time.LocalTime;
 import java.util.Arrays;
 
 public class ManualS7DriverTest extends ManualTest {
@@ -69,41 +72,42 @@ public class ManualS7DriverTest extends ManualTest {
 
     public static void main(String[] args) throws Exception {
         ManualS7DriverTest test = new ManualS7DriverTest("s7://192.168.23.30");
-        test.addTestCase("%DB4:0.0:BOOL", true);
-        test.addTestCase("%DB4:1:BYTE", Arrays.asList(false, false, true, false, true, false, true, false));
-        test.addTestCase("%DB4:2:WORD", Arrays.asList(true, false, true, false, false, true, false, true, true, false, true, true, true, false, false, false));
-        test.addTestCase("%DB4:4:DWORD", Arrays.asList(true, true, true, true, true, true, false, false, true, true, false, true, true, true, true, false, true, false, false, false, true, false, false, false, true, false, true, true, true, false, false, false));
-        test.addTestCase("%DB4:16:SINT", -42);
-        test.addTestCase("%DB4:17:USINT", 42);
-        test.addTestCase("%DB4:18:INT", -2424);
-        test.addTestCase("%DB4:20:UINT", 42424);
-        test.addTestCase("%DB4:22:DINT", -242442424);
-        test.addTestCase("%DB4:26:UDINT", 4242442424L);
+        test.addTestCase("%DB4:0.0:BOOL", new PlcBOOL(true));
+        test.addTestCase("%DB4:1:BYTE", new PlcBitString(new boolean[]{false, false, true, false, true, false, true, false}));
+        test.addTestCase("%DB4:2:WORD", new PlcBitString(new boolean[]{true, false, true, false, false, true, false, true, true, false, true, true, true, false, false, false}));
+        test.addTestCase("%DB4:4:DWORD", new PlcBitString(new boolean[]{true, true, true, true, true, true, false, false, true, true, false, true, true, true, true, false, true, false, false, false, true, false, false, false, true, false, true, true, true, false, false, false}));
+        test.addTestCase("%DB4:16:SINT", new PlcSINT(-42));
+        test.addTestCase("%DB4:17:USINT", new PlcUSINT(42));
+        test.addTestCase("%DB4:18:INT", new PlcINT(-2424));
+        test.addTestCase("%DB4:20:UINT", new PlcUINT(42424));
+        test.addTestCase("%DB4:22:DINT", new PlcDINT(-242442424));
+        test.addTestCase("%DB4:26:UDINT", new PlcUDINT(4242442424L));
         // Not supported in S7 1200
-        //test.addTestCase("%DB4:30:LINT", -4242442424242424242L);
+        //test.addTestCase("%DB4:30:LINT", new PlcLINT(-4242442424242424242L));
         // Not supported in S7 1200
-        //test.addTestCase("%DB4:38:ULINT", 4242442424242424242L);
-        test.addTestCase("%DB4:46:REAL", 3.141593F);
+        //test.addTestCase("%DB4:38:ULINT", new PlcULINT(4242442424242424242L));
+        test.addTestCase("%DB4:46:REAL", new PlcREAL(3.141593F));
         // Not supported in S7 1200
-        //test.addTestCase("%DB4:50:LREAL", 2.71828182846D);
+        //test.addTestCase("%DB4:50:LREAL", new PlcLREAL(2.71828182846D));
         test.addTestCase("%DB4:58:TIME", "PT1.234S");
+        test.addTestCase("%DB4:136:CHAR", new PlcCHAR("H"));
+        test.addTestCase("%DB4:138:WCHAR", new PlcWCHAR("w"));
+        test.addTestCase("%DB4:140:STRING(10)", new PlcSTRING("hurz"));
+        test.addTestCase("%DB4:396:WSTRING(10)", new PlcWSTRING("wolf"));
+        //test.addTestCase("%DB4:70:TIME", new PlcTIME(Duration.parse("PT1.234S"));
         // Not supported in S7 1200
-        //test.addTestCase("%DB4:62:LTIME", "PT24015H23M12.034002044S");
-        test.addTestCase("%DB4:70:DATE", "1998-03-28");
-        test.addTestCase("%DB4:72:TIME_OF_DAY", "15:36:30.123");
-        test.addTestCase("%DB4:76:TOD", "16:17:18.123");
+        //test.addTestCase("%DB4:62:LTIME", new PlcLTIME(Duration.parse("PT24015H23M12.034002044S"));
+        test.addTestCase("%DB4:70:DATE", new PlcDATE(LocalDate.parse("1998-03-28")));
+        test.addTestCase("%DB4:72:TIME_OF_DAY", new PlcTIME_OF_DAY(LocalTime.parse("15:36:30.123")));
+        test.addTestCase("%DB4:76:TOD", new PlcTIME_OF_DAY(LocalTime.parse("16:17:18.123")));
         // Not supported in S7 1200
-        //test.addTestCase("%DB4:96:DATE_AND_TIME", "1996-05-06T15:36:30");
+        //test.addTestCase("%DB4:96:DATE_AND_TIME", new PlcDATE_AND_TIME(LocalDateTime.parse("1996-05-06T15:36:30")));
         // Not supported in S7 1200
         //test.addTestCase("%DB4:104:DT", "1992-03-29T00:00");
         // Not supported in S7 1200
         //test.addTestCase("%DB4:112:LDATE_AND_TIME", "1978-03-28T15:36:30");
         // Not supported in S7 1200
         //test.addTestCase("%DB4:124:LDT", "1978-03-28T15:36:30");
-        test.addTestCase("%DB4:136:CHAR", "H");
-        test.addTestCase("%DB4:138:WCHAR", "w");
-        test.addTestCase("%DB4:140:STRING(10)", "hurz");
-        test.addTestCase("%DB4:396:WSTRING(10)", "wolf");
         test.run();
     }
 
diff --git a/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/generation/WriteBufferByteBased.java b/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/generation/WriteBufferByteBased.java
index 41ae7f830..e7a019f96 100644
--- a/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/generation/WriteBufferByteBased.java
+++ b/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/generation/WriteBufferByteBased.java
@@ -20,6 +20,7 @@ package org.apache.plc4x.java.spi.generation;
 
 import com.github.jinahya.bit.io.BufferByteOutput;
 import org.apache.commons.lang3.ArrayUtils;
+import org.apache.plc4x.java.api.exceptions.PlcRuntimeException;
 import org.apache.plc4x.java.spi.generation.io.MyDefaultBitOutput;
 
 import java.io.IOException;
@@ -27,6 +28,7 @@ import java.math.BigDecimal;
 import java.math.BigInteger;
 import java.nio.ByteBuffer;
 import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
 
 import static org.apache.commons.lang3.ArrayUtils.subarray;
 
@@ -303,9 +305,24 @@ public class WriteBufferByteBased implements WriteBuffer {
 
     @Override
     public void writeString(String logicalName, int bitLength, String encoding, String value, WithWriterArgs... writerArgs) throws SerializationException {
-        final byte[] bytes = value.getBytes(Charset.forName(encoding.replaceAll("[^a-zA-Z0-9]", "")));
-        int fixedByteLength = (int) Math.ceil((float) bitLength / 8.0);
+        byte[] bytes;
+        encoding = encoding.replaceAll("[^a-zA-Z0-9]", "");
+        switch (encoding.toUpperCase()) {
+            case "UTF8": {
+                bytes = value.getBytes(StandardCharsets.UTF_8);
+                break;
+            }
+            case "UTF16":
+            case "UTF16LE":
+            case "UTF16BE": {
+                bytes = value.getBytes(StandardCharsets.UTF_16);
+                break;
+            }
+            default:
+                throw new SerializationException("Unsupported encoding: " + encoding);
+        }
 
+        int fixedByteLength = (int) Math.ceil((float) bitLength / 8.0);
         if (bitLength == 0) {
             fixedByteLength = bytes.length;
         }
diff --git a/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/values/PlcDATE.java b/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/values/PlcDATE.java
index 25d2fef8a..41913929f 100644
--- a/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/values/PlcDATE.java
+++ b/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/values/PlcDATE.java
@@ -32,6 +32,8 @@ import java.time.Instant;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.ZoneId;
+import java.time.temporal.ChronoUnit;
+import java.time.temporal.TemporalField;
 
 @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, property = "className")
 public class PlcDATE extends PlcSimpleValue<LocalDate> {
@@ -70,6 +72,17 @@ public class PlcDATE extends PlcSimpleValue<LocalDate> {
         return PlcValueType.DATE;
     }
 
+    @Override
+    public boolean isLong() {
+        return true;
+    }
+
+    @Override
+    public long getLong() {
+        Instant instant = value.atStartOfDay(ZoneId.systemDefault()).toInstant();
+        return (instant.toEpochMilli() / 1000);
+    }
+
     @Override
     @JsonIgnore
     public boolean isString() {
diff --git a/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/values/PlcDATE_AND_TIME.java b/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/values/PlcDATE_AND_TIME.java
index 0227160f4..1d5121bd7 100644
--- a/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/values/PlcDATE_AND_TIME.java
+++ b/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/values/PlcDATE_AND_TIME.java
@@ -38,7 +38,7 @@ public class PlcDATE_AND_TIME extends PlcSimpleValue<LocalDateTime> {
             return new PlcDATE_AND_TIME((LocalDateTime) value);
         } else if (value instanceof Long) {
             return new PlcDATE_AND_TIME(LocalDateTime.ofInstant(
-                Instant.ofEpochSecond((long) value), ZoneId.of("UTC")));
+                Instant.ofEpochSecond((long) value), ZoneId.systemDefault()));
         }
         throw new PlcRuntimeException("Invalid value type");
     }
@@ -59,6 +59,17 @@ public class PlcDATE_AND_TIME extends PlcSimpleValue<LocalDateTime> {
         return PlcValueType.DATE_AND_TIME;
     }
 
+    @Override
+    public boolean isLong() {
+        return true;
+    }
+
+    @Override
+    public long getLong() {
+        Instant instant = value.atZone(ZoneId.systemDefault()).toInstant();
+        return instant.getEpochSecond();
+    }
+
     @Override
     @JsonIgnore
     public boolean isString() {
diff --git a/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/values/PlcLTIME.java b/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/values/PlcLTIME.java
index 80645d465..6b742a23e 100644
--- a/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/values/PlcLTIME.java
+++ b/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/values/PlcLTIME.java
@@ -68,6 +68,21 @@ public class PlcLTIME extends PlcSimpleValue<Duration> {
         return PlcValueType.LTIME;
     }
 
+    @Override
+    public boolean isInteger() {
+        return true;
+    }
+
+    @Override
+    public boolean isLong() {
+        return true;
+    }
+
+    @Override
+    public boolean isBigInteger() {
+        return true;
+    }
+
     @Override
     @JsonIgnore
     public boolean isString() {
@@ -79,6 +94,21 @@ public class PlcLTIME extends PlcSimpleValue<Duration> {
         return true;
     }
 
+    @Override
+    public int getInteger() {
+        return (int) (value.get(ChronoUnit.NANOS) / 1000000);
+    }
+
+    @Override
+    public long getLong() {
+        return value.get(ChronoUnit.NANOS);
+    }
+
+    @Override
+    public BigInteger getBigInteger() {
+        return BigInteger.valueOf(value.get(ChronoUnit.NANOS));
+    }
+
     @Override
     public Duration getDuration() {
         return value;
diff --git a/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/values/PlcSTRING.java b/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/values/PlcSTRING.java
index 1d8f43af8..d01fe4431 100644
--- a/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/values/PlcSTRING.java
+++ b/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/values/PlcSTRING.java
@@ -232,7 +232,7 @@ public class PlcSTRING extends PlcSimpleValue<String> {
 
     @Override
     public void serialize(WriteBuffer writeBuffer) throws SerializationException {
-        String valueString = value.toString();
+        String valueString = value;
         writeBuffer.writeString(getClass().getSimpleName(), valueString.getBytes(StandardCharsets.UTF_8).length*8,StandardCharsets.UTF_8.name(),valueString);
     }
 
diff --git a/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/values/PlcTIME.java b/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/values/PlcTIME.java
index 72e7bd623..948186233 100644
--- a/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/values/PlcTIME.java
+++ b/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/values/PlcTIME.java
@@ -30,6 +30,7 @@ import org.apache.plc4x.java.spi.generation.WriteBuffer;
 import java.nio.charset.StandardCharsets;
 import java.time.Duration;
 import java.time.temporal.ChronoUnit;
+import java.time.temporal.TemporalUnit;
 
 @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, property = "className")
 public class PlcTIME extends PlcSimpleValue<Duration> {
@@ -63,6 +64,16 @@ public class PlcTIME extends PlcSimpleValue<Duration> {
         return PlcValueType.TIME;
     }
 
+    @Override
+    public boolean isInteger() {
+        return true;
+    }
+
+    @Override
+    public boolean isLong() {
+        return true;
+    }
+
     @Override
     @JsonIgnore
     public boolean isString() {
@@ -74,6 +85,16 @@ public class PlcTIME extends PlcSimpleValue<Duration> {
         return true;
     }
 
+    @Override
+    public int getInteger() {
+        return (int) (value.get(ChronoUnit.NANOS) / 1000000);
+    }
+
+    @Override
+    public long getLong() {
+        return value.get(ChronoUnit.NANOS) / 1000000;
+    }
+
     @Override
     public Duration getDuration() {
         return value;
diff --git a/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/values/PlcTIME_OF_DAY.java b/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/values/PlcTIME_OF_DAY.java
index 90bc0c1a9..56360458a 100644
--- a/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/values/PlcTIME_OF_DAY.java
+++ b/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/values/PlcTIME_OF_DAY.java
@@ -28,7 +28,9 @@ import org.apache.plc4x.java.spi.generation.SerializationException;
 import org.apache.plc4x.java.spi.generation.WriteBuffer;
 
 import java.nio.charset.StandardCharsets;
+import java.time.Instant;
 import java.time.LocalTime;
+import java.time.ZoneId;
 
 @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, property = "className")
 public class PlcTIME_OF_DAY extends PlcSimpleValue<LocalTime> {
@@ -57,6 +59,16 @@ public class PlcTIME_OF_DAY extends PlcSimpleValue<LocalTime> {
         return PlcValueType.TIME_OF_DAY;
     }
 
+    @Override
+    public boolean isLong() {
+        return true;
+    }
+
+    @Override
+    public long getLong() {
+        return ((long) value.toSecondOfDay()) * 1000;
+    }
+
     @Override
     @JsonIgnore
     public boolean isString() {
diff --git a/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/values/PlcValues.java b/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/values/PlcValues.java
index 06d1cf67e..ff98f9f28 100644
--- a/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/values/PlcValues.java
+++ b/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/values/PlcValues.java
@@ -69,11 +69,14 @@ public class PlcValues {
                 Object[] objectArray = (Object[]) o;
                 o = Arrays.asList(objectArray);
             }
+            if (simpleName.equals("Boolean")) {
+                simpleName = "Bool";
+            }
             // If it's one of the LocalDate, LocalTime or LocalDateTime, cut off the "Local".
             if (simpleName.startsWith("Local")) {
                 simpleName = simpleName.substring(5);
             }
-            Constructor<?> constructor = Class.forName(PlcValues.class.getPackage().getName() + ".Plc" + simpleName).getDeclaredConstructor(clazz);
+            Constructor<?> constructor = Class.forName(PlcValues.class.getPackage().getName() + ".Plc" + simpleName.toUpperCase()).getDeclaredConstructor(clazz);
             return ((PlcValue) constructor.newInstance(o));
         } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException | ClassNotFoundException e) {
             LOGGER.warn("Cannot wrap", e);
diff --git a/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/values/PlcSTRING.java b/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/values/PlcWSTRING.java
similarity index 92%
copy from plc4j/spi/src/main/java/org/apache/plc4x/java/spi/values/PlcSTRING.java
copy to plc4j/spi/src/main/java/org/apache/plc4x/java/spi/values/PlcWSTRING.java
index 1d8f43af8..2cf0b0714 100644
--- a/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/values/PlcSTRING.java
+++ b/plc4j/spi/src/main/java/org/apache/plc4x/java/spi/values/PlcWSTRING.java
@@ -31,23 +31,23 @@ import java.math.BigInteger;
 import java.nio.charset.StandardCharsets;
 
 @JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, property = "className")
-public class PlcSTRING extends PlcSimpleValue<String> {
+public class PlcWSTRING extends PlcSimpleValue<String> {
 
-    public static PlcSTRING of(Object value) {
+    public static PlcWSTRING of(Object value) {
         if (value instanceof String) {
-            return new PlcSTRING((String) value);
+            return new PlcWSTRING((String) value);
         }
-        return new PlcSTRING(String.valueOf(value));
+        return new PlcWSTRING(String.valueOf(value));
     }
 
     @JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
-    public PlcSTRING(@JsonProperty("value") String value) {
+    public PlcWSTRING(@JsonProperty("value") String value) {
         super(value, true);
     }
 
     @Override
     public PlcValueType getPlcValueType() {
-        return PlcValueType.STRING;
+        return PlcValueType.WSTRING;
     }
 
     @Override
@@ -232,8 +232,8 @@ public class PlcSTRING extends PlcSimpleValue<String> {
 
     @Override
     public void serialize(WriteBuffer writeBuffer) throws SerializationException {
-        String valueString = value.toString();
-        writeBuffer.writeString(getClass().getSimpleName(), valueString.getBytes(StandardCharsets.UTF_8).length*8,StandardCharsets.UTF_8.name(),valueString);
+        String valueString = value;
+        writeBuffer.writeString(getClass().getSimpleName(), valueString.getBytes(StandardCharsets.UTF_16).length*8,StandardCharsets.UTF_16.name(),valueString);
     }
 
 }
diff --git a/plc4j/utils/test-utils/src/main/java/org/apache/plc4x/test/manual/ManualTest.java b/plc4j/utils/test-utils/src/main/java/org/apache/plc4x/test/manual/ManualTest.java
index 92e4733c3..018329b36 100644
--- a/plc4j/utils/test-utils/src/main/java/org/apache/plc4x/test/manual/ManualTest.java
+++ b/plc4j/utils/test-utils/src/main/java/org/apache/plc4x/test/manual/ManualTest.java
@@ -22,8 +22,13 @@ import org.apache.plc4x.java.PlcDriverManager;
 import org.apache.plc4x.java.api.PlcConnection;
 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.api.types.PlcResponseCode;
+import org.apache.plc4x.java.api.value.PlcValue;
+import org.apache.plc4x.java.spi.values.PlcBitString;
 import org.apache.plc4x.java.spi.values.PlcList;
+import org.apache.plc4x.java.spi.values.PlcValues;
 import org.junit.jupiter.api.Assertions;
 
 import java.util.ArrayList;
@@ -33,10 +38,16 @@ import java.util.List;
 public abstract class ManualTest {
 
     private final String connectionString;
+    private final boolean testWrite;
     private final List<TestCase> testCases;
 
     public ManualTest(String connectionString) {
+        this(connectionString, false);
+    }
+
+    public ManualTest(String connectionString, boolean testWrite) {
         this.connectionString = connectionString;
+        this.testWrite = testWrite;
         testCases = new ArrayList<>();
     }
 
@@ -63,17 +74,50 @@ public abstract class ManualTest {
                 Assertions.assertNotNull(readResponse.getPlcValue(fieldName), fieldName);
                 if(readResponse.getPlcValue(fieldName) instanceof PlcList) {
                     PlcList plcList = (PlcList) readResponse.getPlcValue(fieldName);
-                    if(testCase.expectedReadValue instanceof List) {
-                        List<Object> expectedValues = (List<Object>) testCase.expectedReadValue;
-                        for (int j = 0; j < expectedValues.size(); j++) {
-                            Assertions.assertEquals(expectedValues.get(j), plcList.getIndex(j).getObject(), fieldName + "[" + j + "]");
-                        }
+                    List expectedValues;
+                    if (testCase.expectedReadValue instanceof PlcList) {
+                        PlcList expectedPlcList = (PlcList) testCase.expectedReadValue;
+                        expectedValues = expectedPlcList.getList();
+                    } else if(testCase.expectedReadValue instanceof List) {
+                        expectedValues = (List) testCase.expectedReadValue;
                     } else {
                         Assertions.fail("Got a list of values, but only expected one.");
+                        return;
+                    }
+                    for (int j = 0; j < expectedValues.size(); j++) {
+                        if (expectedValues.get(j) instanceof PlcValue) {
+                            Assertions.assertEquals(((PlcValue) expectedValues.get(j)).getObject(), plcList.getIndex(j).getObject(), fieldName + "[" + j + "]");
+                        } else {
+                            Assertions.assertEquals(expectedValues.get(j), plcList.getIndex(j).getObject(), fieldName + "[" + j + "]");
+                        }
                     }
                 } else {
-                    Assertions.assertEquals(
-                        testCase.expectedReadValue.toString(), readResponse.getPlcValue(fieldName).getObject().toString(), fieldName);
+                    if (testCase.expectedReadValue instanceof PlcValue) {
+                        Assertions.assertEquals(
+                            ((PlcValue) testCase.expectedReadValue).getObject(), readResponse.getPlcValue(fieldName).getObject(), fieldName);
+                    } else {
+                        Assertions.assertEquals(
+                            testCase.expectedReadValue.toString(), readResponse.getPlcValue(fieldName).getObject().toString(), fieldName);
+                    }
+                }
+
+                // Try writing the same value back to the PLC.
+                if(testWrite) {
+                    PlcValue plcValue;
+                    if(testCase.expectedReadValue instanceof PlcValue) {
+                        plcValue = ((PlcValue) testCase.expectedReadValue);
+                    } else {
+                        plcValue = PlcValues.of(testCase.expectedReadValue);
+                    }
+
+                    // Prepare the write request
+                    PlcWriteRequest writeRequest = plcConnection.writeRequestBuilder().addItem(fieldName, testCase.address, plcValue).build();
+
+                    // Execute the write request
+                    PlcWriteResponse writeResponse = writeRequest.execute().get();
+
+                    // Check the result
+                    Assertions.assertEquals(PlcResponseCode.OK, writeResponse.getResponseCode(fieldName));
                 }
             }
             System.out.println("Success");
@@ -110,16 +154,23 @@ public abstract class ManualTest {
                     Assertions.assertEquals(PlcResponseCode.OK, readResponse.getResponseCode(fieldName),
                         "Field: " + fieldName);
                     Assertions.assertNotNull(readResponse.getPlcValue(fieldName), "Field: " + fieldName);
-                    if (readResponse.getPlcValue(fieldName) instanceof PlcList) {
+                    if (readResponse.getPlcValue(fieldName) instanceof PlcBitString) {
+                        PlcBitString plcBitString = (PlcBitString) readResponse.getPlcValue(fieldName);
+                        List<PlcValue> expectedValues = ((PlcBitString) testCase.expectedReadValue).getList();
+                        for (int j = 0; j < expectedValues.size(); j++) {
+                            Assertions.assertEquals(expectedValues.get(j).toString(), plcBitString.getIndex(j).toString(),
+                                "Field: " + fieldName);
+                        }
+                    } else if (readResponse.getPlcValue(fieldName) instanceof PlcList) {
                         PlcList plcList = (PlcList) readResponse.getPlcValue(fieldName);
                         List<Object> expectedValues = (List<Object>) testCase.expectedReadValue;
                         for (int j = 0; j < expectedValues.size(); j++) {
-                            Assertions.assertEquals(expectedValues.get(j), plcList.getIndex(j).getObject(),
+                            Assertions.assertEquals(expectedValues.get(j), plcList.getIndex(j),
                                 "Field: " + fieldName);
                         }
                     } else {
                         Assertions.assertEquals(
-                            testCase.expectedReadValue.toString(), readResponse.getPlcValue(fieldName).getObject().toString(),
+                            testCase.expectedReadValue.toString(), readResponse.getPlcValue(fieldName).toString(),
                             "Field: " + fieldName);
                     }
                 }