You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@plc4x.apache.org by sr...@apache.org on 2018/07/05 15:42:02 UTC

[incubator-plc4x] branch master updated: added Test for Plc4XModbusProtocol and fixed several Bugs.

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

sruehl pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-plc4x.git


The following commit(s) were added to refs/heads/master by this push:
     new 22d5106  added Test for Plc4XModbusProtocol and fixed several Bugs.
22d5106 is described below

commit 22d5106948d4723a7077d40e84ff4c63c554fabf
Author: Sebastian Rühl <sr...@apache.org>
AuthorDate: Thu Jul 5 17:41:56 2018 +0200

    added Test for Plc4XModbusProtocol and fixed several Bugs.
---
 .../modbus/connection/BaseModbusPlcConnection.java |   4 +-
 .../plc4x/java/modbus/model/CoilModbusAddress.java |   2 +-
 .../model/MaskWriteRegisterModbusAddress.java      |   2 +-
 .../model/ReadDiscreteInputsModbusAddress.java     |   2 +-
 .../model/ReadHoldingRegistersModbusAddress.java   |   2 +-
 .../model/ReadInputRegistersModbusAddress.java     |   2 +-
 ...sterAddress.java => RegisterModbusAddress.java} |  10 +-
 .../java/modbus/netty/Plc4XModbusProtocol.java     |  42 ++-
 .../connection/ModbusSerialPlcConnectionTest.java  |   2 +-
 .../connection/ModbusTcpPlcConnectionTests.java    |   2 +-
 .../java/modbus/netty/Plc4XModbusProtocolTest.java | 294 +++++++++++++++++++--
 11 files changed, 317 insertions(+), 47 deletions(-)

diff --git a/plc4j/protocols/modbus/src/main/java/org/apache/plc4x/java/modbus/connection/BaseModbusPlcConnection.java b/plc4j/protocols/modbus/src/main/java/org/apache/plc4x/java/modbus/connection/BaseModbusPlcConnection.java
index 0056363..6be492d 100644
--- a/plc4j/protocols/modbus/src/main/java/org/apache/plc4x/java/modbus/connection/BaseModbusPlcConnection.java
+++ b/plc4j/protocols/modbus/src/main/java/org/apache/plc4x/java/modbus/connection/BaseModbusPlcConnection.java
@@ -68,8 +68,8 @@ public abstract class BaseModbusPlcConnection extends AbstractPlcConnection impl
             return ReadInputRegistersModbusAddress.of(addressString);
         } else if (CoilModbusAddress.ADDRESS_PATTERN.matcher(addressString).matches()) {
             return CoilModbusAddress.of(addressString);
-        } else if (RegisterAddress.ADDRESS_PATTERN.matcher(addressString).matches()) {
-            return RegisterAddress.of(addressString);
+        } else if (RegisterModbusAddress.ADDRESS_PATTERN.matcher(addressString).matches()) {
+            return RegisterModbusAddress.of(addressString);
         }
         return null;
     }
diff --git a/plc4j/protocols/modbus/src/main/java/org/apache/plc4x/java/modbus/model/CoilModbusAddress.java b/plc4j/protocols/modbus/src/main/java/org/apache/plc4x/java/modbus/model/CoilModbusAddress.java
index 414c968..19681b0 100644
--- a/plc4j/protocols/modbus/src/main/java/org/apache/plc4x/java/modbus/model/CoilModbusAddress.java
+++ b/plc4j/protocols/modbus/src/main/java/org/apache/plc4x/java/modbus/model/CoilModbusAddress.java
@@ -34,7 +34,7 @@ public class CoilModbusAddress extends ModbusAddress {
     public static CoilModbusAddress of(String addressString) {
         Matcher matcher = ADDRESS_PATTERN.matcher(addressString);
         if (!matcher.matches()) {
-            throw new PlcRuntimeException(addressString + " doesn't match" + ADDRESS_PATTERN);
+            throw new PlcRuntimeException(addressString + " doesn't match " + ADDRESS_PATTERN);
         }
         int address = Integer.valueOf(matcher.group("address"));
         return new CoilModbusAddress(address);
diff --git a/plc4j/protocols/modbus/src/main/java/org/apache/plc4x/java/modbus/model/MaskWriteRegisterModbusAddress.java b/plc4j/protocols/modbus/src/main/java/org/apache/plc4x/java/modbus/model/MaskWriteRegisterModbusAddress.java
index fe61796..1709799 100644
--- a/plc4j/protocols/modbus/src/main/java/org/apache/plc4x/java/modbus/model/MaskWriteRegisterModbusAddress.java
+++ b/plc4j/protocols/modbus/src/main/java/org/apache/plc4x/java/modbus/model/MaskWriteRegisterModbusAddress.java
@@ -40,7 +40,7 @@ public class MaskWriteRegisterModbusAddress extends ModbusAddress {
     public static MaskWriteRegisterModbusAddress of(String addressString) {
         Matcher matcher = ADDRESS_PATTERN.matcher(addressString);
         if (!matcher.matches()) {
-            throw new PlcRuntimeException(addressString + " doesn't match" + ADDRESS_PATTERN);
+            throw new PlcRuntimeException(addressString + " doesn't match " + ADDRESS_PATTERN);
         }
         int address = Integer.valueOf(matcher.group("address"));
         int andMask = Integer.valueOf(matcher.group("andMask"));
diff --git a/plc4j/protocols/modbus/src/main/java/org/apache/plc4x/java/modbus/model/ReadDiscreteInputsModbusAddress.java b/plc4j/protocols/modbus/src/main/java/org/apache/plc4x/java/modbus/model/ReadDiscreteInputsModbusAddress.java
index 2433e26..7adf4e2 100644
--- a/plc4j/protocols/modbus/src/main/java/org/apache/plc4x/java/modbus/model/ReadDiscreteInputsModbusAddress.java
+++ b/plc4j/protocols/modbus/src/main/java/org/apache/plc4x/java/modbus/model/ReadDiscreteInputsModbusAddress.java
@@ -34,7 +34,7 @@ public class ReadDiscreteInputsModbusAddress extends ModbusAddress {
     public static ReadDiscreteInputsModbusAddress of(String addressString) {
         Matcher matcher = ADDRESS_PATTERN.matcher(addressString);
         if (!matcher.matches()) {
-            throw new PlcRuntimeException(addressString + " doesn't match" + ADDRESS_PATTERN);
+            throw new PlcRuntimeException(addressString + " doesn't match " + ADDRESS_PATTERN);
         }
         int address = Integer.valueOf(matcher.group("address"));
         return new ReadDiscreteInputsModbusAddress(address);
diff --git a/plc4j/protocols/modbus/src/main/java/org/apache/plc4x/java/modbus/model/ReadHoldingRegistersModbusAddress.java b/plc4j/protocols/modbus/src/main/java/org/apache/plc4x/java/modbus/model/ReadHoldingRegistersModbusAddress.java
index 4d222f8..847cb29 100644
--- a/plc4j/protocols/modbus/src/main/java/org/apache/plc4x/java/modbus/model/ReadHoldingRegistersModbusAddress.java
+++ b/plc4j/protocols/modbus/src/main/java/org/apache/plc4x/java/modbus/model/ReadHoldingRegistersModbusAddress.java
@@ -34,7 +34,7 @@ public class ReadHoldingRegistersModbusAddress extends ModbusAddress {
     public static ReadHoldingRegistersModbusAddress of(String addressString) {
         Matcher matcher = ADDRESS_PATTERN.matcher(addressString);
         if (!matcher.matches()) {
-            throw new PlcRuntimeException(addressString + " doesn't match" + ADDRESS_PATTERN);
+            throw new PlcRuntimeException(addressString + " doesn't match " + ADDRESS_PATTERN);
         }
         int address = Integer.valueOf(matcher.group("address"));
         return new ReadHoldingRegistersModbusAddress(address);
diff --git a/plc4j/protocols/modbus/src/main/java/org/apache/plc4x/java/modbus/model/ReadInputRegistersModbusAddress.java b/plc4j/protocols/modbus/src/main/java/org/apache/plc4x/java/modbus/model/ReadInputRegistersModbusAddress.java
index 36172ab..663fbe1 100644
--- a/plc4j/protocols/modbus/src/main/java/org/apache/plc4x/java/modbus/model/ReadInputRegistersModbusAddress.java
+++ b/plc4j/protocols/modbus/src/main/java/org/apache/plc4x/java/modbus/model/ReadInputRegistersModbusAddress.java
@@ -34,7 +34,7 @@ public class ReadInputRegistersModbusAddress extends ModbusAddress {
     public static ReadInputRegistersModbusAddress of(String addressString) {
         Matcher matcher = ADDRESS_PATTERN.matcher(addressString);
         if (!matcher.matches()) {
-            throw new PlcRuntimeException(addressString + " doesn't match" + ADDRESS_PATTERN);
+            throw new PlcRuntimeException(addressString + " doesn't match " + ADDRESS_PATTERN);
         }
         int address = Integer.valueOf(matcher.group("address"));
         return new ReadInputRegistersModbusAddress(address);
diff --git a/plc4j/protocols/modbus/src/main/java/org/apache/plc4x/java/modbus/model/RegisterAddress.java b/plc4j/protocols/modbus/src/main/java/org/apache/plc4x/java/modbus/model/RegisterModbusAddress.java
similarity index 84%
rename from plc4j/protocols/modbus/src/main/java/org/apache/plc4x/java/modbus/model/RegisterAddress.java
rename to plc4j/protocols/modbus/src/main/java/org/apache/plc4x/java/modbus/model/RegisterModbusAddress.java
index 69fe451..ac112d8 100644
--- a/plc4j/protocols/modbus/src/main/java/org/apache/plc4x/java/modbus/model/RegisterAddress.java
+++ b/plc4j/protocols/modbus/src/main/java/org/apache/plc4x/java/modbus/model/RegisterModbusAddress.java
@@ -24,20 +24,20 @@ import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 // TODO: Default to {@link ReadHoldingRegistersModbusAddress}
-public class RegisterAddress extends ModbusAddress {
+public class RegisterModbusAddress extends ModbusAddress {
 
     public static final Pattern ADDRESS_PATTERN = Pattern.compile("register:" + ModbusAddress.ADDRESS_PATTERN);
 
-    protected RegisterAddress(int address) {
+    protected RegisterModbusAddress(int address) {
         super(address);
     }
 
-    public static RegisterAddress of(String addressString) {
+    public static RegisterModbusAddress of(String addressString) {
         Matcher matcher = ADDRESS_PATTERN.matcher(addressString);
         if (!matcher.matches()) {
-            throw new PlcRuntimeException(addressString + " doesn't match" + ADDRESS_PATTERN);
+            throw new PlcRuntimeException(addressString + " doesn't match " + ADDRESS_PATTERN);
         }
         int address = Integer.valueOf(matcher.group("address"));
-        return new RegisterAddress(address);
+        return new RegisterModbusAddress(address);
     }
 }
diff --git a/plc4j/protocols/modbus/src/main/java/org/apache/plc4x/java/modbus/netty/Plc4XModbusProtocol.java b/plc4j/protocols/modbus/src/main/java/org/apache/plc4x/java/modbus/netty/Plc4XModbusProtocol.java
index 8f751fa..c5b9698 100644
--- a/plc4j/protocols/modbus/src/main/java/org/apache/plc4x/java/modbus/netty/Plc4XModbusProtocol.java
+++ b/plc4j/protocols/modbus/src/main/java/org/apache/plc4x/java/modbus/netty/Plc4XModbusProtocol.java
@@ -37,6 +37,7 @@ import org.apache.plc4x.java.modbus.model.*;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.util.Collections;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.concurrent.ConcurrentHashMap;
@@ -79,24 +80,23 @@ public class Plc4XModbusProtocol extends MessageToMessageCodec<ModbusTcpPayload,
 
         ModbusAddress address = (ModbusAddress) writeRequestItem.getAddress();
         ModbusPdu modbusRequest;
-        if (address instanceof RegisterAddress) {
-            RegisterAddress registerAddress = (RegisterAddress) address;
+        if (address instanceof RegisterModbusAddress) {
+            RegisterModbusAddress registerModbusAddress = (RegisterModbusAddress) address;
             if (quantity > 1) {
                 byte[] bytesToWrite = produceRegisterValue(writeRequestItem.getValues());
-                modbusRequest = new WriteMultipleRegistersRequest(registerAddress.getAddress(), quantity, bytesToWrite);
+                modbusRequest = new WriteMultipleRegistersRequest(registerModbusAddress.getAddress(), quantity, bytesToWrite);
             } else {
                 byte[] register = produceRegisterValue(writeRequestItem.getValues());
                 int intToWrite = register[0] << 8 | register[1];
-                modbusRequest = new WriteSingleRegisterRequest(registerAddress.getAddress(), intToWrite);
+                modbusRequest = new WriteSingleRegisterRequest(registerModbusAddress.getAddress(), intToWrite);
             }
         } else if (address instanceof CoilModbusAddress) {
             CoilModbusAddress coilModbusAddress = (CoilModbusAddress) address;
             if (quantity > 1) {
-                byte[] bytesToWrite = produceCoilValue(writeRequestItem.getValues());
+                byte[] bytesToWrite = produceCoilValues(writeRequestItem.getValues());
                 modbusRequest = new WriteMultipleCoilsRequest(coilModbusAddress.getAddress(), quantity, bytesToWrite);
             } else {
-                byte[] coil = produceCoilValue(writeRequestItem.getValues());
-                boolean booleanToWrite = (coil[0] >> 8) == 1;
+                boolean booleanToWrite = produceCoilValue(writeRequestItem.getValues());
                 modbusRequest = new WriteSingleCoilRequest(coilModbusAddress.getAddress(), booleanToWrite);
             }
         } else {
@@ -122,9 +122,9 @@ public class Plc4XModbusProtocol extends MessageToMessageCodec<ModbusTcpPayload,
         if (address instanceof CoilModbusAddress) {
             CoilModbusAddress coilModbusAddress = (CoilModbusAddress) address;
             modbusRequest = new ReadCoilsRequest(coilModbusAddress.getAddress(), quantity);
-        } else if (address instanceof RegisterAddress) {
-            RegisterAddress registerAddress = (RegisterAddress) address;
-            modbusRequest = new ReadHoldingRegistersRequest(registerAddress.getAddress(), quantity);
+        } else if (address instanceof RegisterModbusAddress) {
+            RegisterModbusAddress registerModbusAddress = (RegisterModbusAddress) address;
+            modbusRequest = new ReadHoldingRegistersRequest(registerModbusAddress.getAddress(), quantity);
         } else if (address instanceof ReadDiscreteInputsModbusAddress) {
             ReadDiscreteInputsModbusAddress readDiscreteInputsModbusAddress = (ReadDiscreteInputsModbusAddress) address;
             modbusRequest = new ReadDiscreteInputsRequest(readDiscreteInputsModbusAddress.getAddress(), quantity);
@@ -151,7 +151,7 @@ public class Plc4XModbusProtocol extends MessageToMessageCodec<ModbusTcpPayload,
         short transactionId = msg.getTransactionId();
         PlcRequestContainer<PlcRequest, PlcResponse> plcRequestContainer = requestsMap.get(transactionId);
         if (plcRequestContainer == null) {
-            throw new PlcProtocolException("Unrelated payload received" + msg);
+            throw new PlcProtocolException("Unrelated payload received. [transactionId: " + msg.getTransactionId() + ", unitId: " + msg.getUnitId() + ", modbusPdu: " + msg.getModbusPdu() + "]");
         }
 
         // TODO: only single Item supported for now
@@ -246,7 +246,16 @@ public class Plc4XModbusProtocol extends MessageToMessageCodec<ModbusTcpPayload,
     ////////////////////////////////////////////////////////////////////////////////
     // Encoding helpers.
     ////////////////////////////////////////////////////////////////////////////////
-    private byte[] produceCoilValue(List<?> values) throws PlcProtocolException {
+
+    private boolean produceCoilValue(List<?> values) throws PlcProtocolException {
+        if (values.size() != 1) {
+            throw new PlcProtocolException("Only one value allowed");
+        }
+        Byte multiCoil = produceCoilValues(values)[0];
+        return multiCoil != 0;
+    }
+
+    private byte[] produceCoilValues(List<?> values) throws PlcProtocolException {
         List<Byte> coils = new LinkedList<>();
         Byte actualCoil = 0;
         int i = 7;
@@ -289,6 +298,10 @@ public class Plc4XModbusProtocol extends MessageToMessageCodec<ModbusTcpPayload,
                 i = 8;
             }
         }
+        if (coils.isEmpty()) {
+            // We only have one coil
+            return new byte[]{actualCoil};
+        }
         // TODO: ensure we have a least (quantity + 7) / 8 = N bytes
         return ArrayUtils.toPrimitive(coils.toArray(new Byte[0]));
     }
@@ -309,7 +322,7 @@ public class Plc4XModbusProtocol extends MessageToMessageCodec<ModbusTcpPayload,
                 }
                 buffer.writeBytes(bytes);
             } else if (value.getClass() == Short.class) {
-                buffer.writeShort((int) value);
+                buffer.writeShort((short) value);
             } else if (value.getClass() == Integer.class) {
                 if ((int) value > Integer.MAX_VALUE) {
                     throw new PlcProtocolException("Value to high to fit into register: " + value);
@@ -331,6 +344,9 @@ public class Plc4XModbusProtocol extends MessageToMessageCodec<ModbusTcpPayload,
     private List produceCoilValueList(RequestItem requestItem, Class datatype, ByteBuf byteBuf) {
         ReadRequestItem readRequestItem = (ReadRequestItem) requestItem;
         byte[] bytes = new byte[byteBuf.readableBytes()];
+        if (bytes.length < 1) {
+            return Collections.emptyList();
+        }
         byteBuf.readBytes(bytes);
         List data = new LinkedList();
         for (int i = 0, j = 0; i < readRequestItem.getSize(); i++) {
diff --git a/plc4j/protocols/modbus/src/test/java/org/apache/plc4x/java/modbus/connection/ModbusSerialPlcConnectionTest.java b/plc4j/protocols/modbus/src/test/java/org/apache/plc4x/java/modbus/connection/ModbusSerialPlcConnectionTest.java
index 4339c42..ce2fe47 100644
--- a/plc4j/protocols/modbus/src/test/java/org/apache/plc4x/java/modbus/connection/ModbusSerialPlcConnectionTest.java
+++ b/plc4j/protocols/modbus/src/test/java/org/apache/plc4x/java/modbus/connection/ModbusSerialPlcConnectionTest.java
@@ -126,7 +126,7 @@ public class ModbusSerialPlcConnectionTest {
     @Test
     public void parseRegisterAddress() {
         try {
-            RegisterAddress address = (RegisterAddress) SUT.parseAddress("0/1");
+            RegisterModbusAddress address = (RegisterModbusAddress) SUT.parseAddress("0/1");
             assertEquals(address.getAddress(), 0);
         } catch (IllegalArgumentException exception) {
             fail("valid data block address");
diff --git a/plc4j/protocols/modbus/src/test/java/org/apache/plc4x/java/modbus/connection/ModbusTcpPlcConnectionTests.java b/plc4j/protocols/modbus/src/test/java/org/apache/plc4x/java/modbus/connection/ModbusTcpPlcConnectionTests.java
index 10293e3..3640e34 100644
--- a/plc4j/protocols/modbus/src/test/java/org/apache/plc4x/java/modbus/connection/ModbusTcpPlcConnectionTests.java
+++ b/plc4j/protocols/modbus/src/test/java/org/apache/plc4x/java/modbus/connection/ModbusTcpPlcConnectionTests.java
@@ -124,7 +124,7 @@ public class ModbusTcpPlcConnectionTests {
     @Test
     public void parseRegisterAddress() {
         try {
-            RegisterAddress address = (RegisterAddress) SUT.parseAddress("register:0");
+            RegisterModbusAddress address = (RegisterModbusAddress) SUT.parseAddress("register:0");
             assertEquals(address.getAddress(), 0);
         } catch (IllegalArgumentException exception) {
             fail("valid data block address");
diff --git a/plc4j/protocols/modbus/src/test/java/org/apache/plc4x/java/modbus/netty/Plc4XModbusProtocolTest.java b/plc4j/protocols/modbus/src/test/java/org/apache/plc4x/java/modbus/netty/Plc4XModbusProtocolTest.java
index 5926373..685efba 100644
--- a/plc4j/protocols/modbus/src/test/java/org/apache/plc4x/java/modbus/netty/Plc4XModbusProtocolTest.java
+++ b/plc4j/protocols/modbus/src/test/java/org/apache/plc4x/java/modbus/netty/Plc4XModbusProtocolTest.java
@@ -18,32 +18,286 @@
  */
 package org.apache.plc4x.java.modbus.netty;
 
-import org.apache.commons.lang3.reflect.MethodUtils;
+import com.digitalpetri.modbus.ModbusPdu;
+import com.digitalpetri.modbus.codec.ModbusTcpPayload;
+import com.digitalpetri.modbus.requests.*;
+import com.digitalpetri.modbus.responses.*;
+import io.netty.buffer.Unpooled;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.tuple.ImmutablePair;
+import org.apache.plc4x.java.api.messages.*;
+import org.apache.plc4x.java.api.messages.items.ReadResponseItem;
+import org.apache.plc4x.java.api.messages.items.ResponseItem;
+import org.apache.plc4x.java.modbus.model.*;
+import org.junit.Before;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
-import java.util.Arrays;
-import java.util.BitSet;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collection;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertSame;
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.assertThat;
 
+@RunWith(Parameterized.class)
 public class Plc4XModbusProtocolTest {
 
+    private static final Logger LOGGER = LoggerFactory.getLogger(Plc4XModbusProtocolTest.class);
+
+    public static final Calendar calenderInstance = Calendar.getInstance();
+
+    private Plc4XModbusProtocol SUT;
+
+    @Parameterized.Parameter
+    public String payloadClazzName;
+
+    @Parameterized.Parameter(1)
+    public PlcRequestContainer<PlcRequest, PlcResponse> plcRequestContainer;
+
+    @Parameterized.Parameter(2)
+    public CompletableFuture completableFuture;
+
+    @Parameterized.Parameter(3)
+    public String plcRequestContainerClassName;
+
+    @Parameterized.Parameter(4)
+    public ModbusTcpPayload modbusTcpPayload;
+
+    @Parameterized.Parameter(5)
+    public String modbusPduName;
+
+    @Parameterized.Parameters(name = "{index} Type:{0} {3} {5}")
+    public static Collection<Object[]> data() {
+        return Stream.of(
+            Boolean.class,
+            Byte.class,
+            Short.class,
+            //Calendar.class,
+            //Float.class,
+            Integer.class //,
+            //String.class
+        )
+            .map(clazz -> {
+                if (clazz == Boolean.class) {
+                    return ImmutablePair.of(Boolean.TRUE, new byte[]{0x01});
+                } else if (clazz == Byte.class) {
+                    return ImmutablePair.of(Byte.valueOf("1"), new byte[]{0x1});
+                } else if (clazz == Short.class) {
+                    return ImmutablePair.of(Short.valueOf("1"), new byte[]{0x1, 0x0});
+                } else if (clazz == Calendar.class) {
+                    return ImmutablePair.of(calenderInstance, new byte[]{0x0, 0x0, 0x0, 0x0, 0x4, 0x3, 0x2, 0x1});
+                } else if (clazz == Float.class) {
+                    return ImmutablePair.of(Float.valueOf("1"), new byte[]{0x0, 0x0, (byte) 0x80, 0x3F});
+                } else if (clazz == Integer.class) {
+                    return ImmutablePair.of(Integer.valueOf("1"), new byte[]{0x1, 0x0, 0x0, 0x0});
+                } else if (clazz == String.class) {
+                    return ImmutablePair.of(String.valueOf("Hello World!"), new byte[]{0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72, 0x6c, 0x64, 0x21, 0x00});
+                } else {
+                    throw new IllegalArgumentException("Unmapped type " + clazz);
+                }
+            })
+            .map(pair -> Stream.of(
+                ImmutablePair.of(
+                    new PlcRequestContainer<>(
+                        PlcReadRequest
+                            .builder()
+                            .addItem(pair.left.getClass(), CoilModbusAddress.of("coil:1"))
+                            .build(), new CompletableFuture<>()),
+                    new ModbusTcpPayload((short) 0, (short) 0, new ReadCoilsResponse(Unpooled.wrappedBuffer(pair.right)))
+                ),
+                ImmutablePair.of(
+                    new PlcRequestContainer<>(
+                        PlcReadRequest
+                            .builder()
+                            .addItem(pair.left.getClass(), ReadDiscreteInputsModbusAddress.of("readdiscreteinputs:1"))
+                            .build(), new CompletableFuture<>()),
+                    new ModbusTcpPayload((short) 0, (short) 0, new ReadDiscreteInputsResponse(Unpooled.wrappedBuffer(pair.right)))
+                ),
+                ImmutablePair.of(
+                    new PlcRequestContainer<>(
+                        PlcReadRequest
+                            .builder()
+                            .addItem(pair.left.getClass(), ReadHoldingRegistersModbusAddress.of("readholdingregisters:1"))
+                            .build(), new CompletableFuture<>()),
+                    new ModbusTcpPayload((short) 0, (short) 0, new ReadHoldingRegistersResponse(Unpooled.wrappedBuffer(evenUp(pair.right))))
+                ),
+                ImmutablePair.of(
+                    new PlcRequestContainer<>(
+                        PlcReadRequest
+                            .builder()
+                            .addItem(pair.left.getClass(), ReadInputRegistersModbusAddress.of("readinputregisters:1"))
+                            .build(), new CompletableFuture<>()),
+                    new ModbusTcpPayload((short) 0, (short) 0, new ReadInputRegistersResponse(Unpooled.wrappedBuffer(evenUp(pair.right))))
+                ),
+                ImmutablePair.of(
+                    new PlcRequestContainer<>(
+                        PlcWriteRequest
+                            .builder()
+                            .addItem((Class) pair.left.getClass(), CoilModbusAddress.of("coil:1"), pair.left, pair.left, pair.left)
+                            .build(), new CompletableFuture<>()),
+                    new ModbusTcpPayload((short) 0, (short) 0, new WriteMultipleCoilsResponse(1, 3))
+                ),
+                ImmutablePair.of(
+                    new PlcRequestContainer<>(
+                        PlcWriteRequest
+                            .builder()
+                            .addItem((Class) pair.left.getClass(), RegisterModbusAddress.of("register:1"), pair.left, pair.left, pair.left)
+                            .build(), new CompletableFuture<>()),
+                    new ModbusTcpPayload((short) 0, (short) 0, new WriteMultipleCoilsResponse(1, 3))
+                ),
+                ImmutablePair.of(
+                    new PlcRequestContainer<>(
+                        PlcWriteRequest
+                            .builder()
+                            .addItem(CoilModbusAddress.of("coil:1"), pair.left)
+                            .build(), new CompletableFuture<>()),
+                    new ModbusTcpPayload((short) 0, (short) 0, new WriteSingleCoilResponse(1, pair.right[0]))
+                ),
+                ImmutablePair.of(
+                    new PlcRequestContainer<>(
+                        PlcWriteRequest
+                            .builder()
+                            .addItem(RegisterModbusAddress.of("register:1"), pair.left)
+                            .build(), new CompletableFuture<>()),
+                    new ModbusTcpPayload((short) 0, (short) 0, new WriteSingleCoilResponse(1, pair.right[0]))
+                )
+            ))
+            .flatMap(stream -> stream)
+            .map(pair -> new Object[]{pair.left.getRequest().getRequestItem().orElseThrow(IllegalStateException::new).getDatatype().getSimpleName(), pair.left, pair.left.getResponseFuture(), pair.left.getRequest().getClass().getSimpleName(), pair.right, pair.right.getModbusPdu().getClass().getSimpleName()}).collect(Collectors.toList());
+    }
+
+    private static byte[] evenUp(byte[] bytes) {
+        if (bytes.length % 2 == 0) {
+            return bytes;
+        } else {
+            return ArrayUtils.insert(0, bytes, (byte) 0x0);
+        }
+    }
+
+    @Before
+    public void setUp() {
+        SUT = new Plc4XModbusProtocol();
+    }
+
+    @Test
+    public void encode() throws Exception {
+        ArrayList<Object> out = new ArrayList<>();
+        SUT.encode(null, plcRequestContainer, out);
+        assertThat(out, hasSize(1));
+        assertThat(out.get(0), instanceOf(ModbusTcpPayload.class));
+        ModbusTcpPayload modbusTcpPayload = (ModbusTcpPayload) out.get(0);
+        ModbusPdu modbusPdu = modbusTcpPayload.getModbusPdu();
+        if (modbusPdu instanceof MaskWriteRegisterRequest) {
+            // TODO: implement me
+        } else if (modbusPdu instanceof ReadCoilsRequest) {
+            // TODO: implement me
+        } else if (modbusPdu instanceof ReadDiscreteInputsRequest) {
+            // TODO: implement me
+        } else if (modbusPdu instanceof ReadHoldingRegistersRequest) {
+            // TODO: implement me
+        } else if (modbusPdu instanceof ReadInputRegistersRequest) {
+            // TODO: implement me
+        } else if (modbusPdu instanceof WriteMultipleCoilsRequest) {
+            // TODO: implement me
+        } else if (modbusPdu instanceof WriteMultipleRegistersRequest) {
+            // TODO: implement me
+        } else if (modbusPdu instanceof WriteSingleCoilRequest) {
+            WriteSingleCoilRequest writeSingleCoilRequest = (WriteSingleCoilRequest) modbusPdu;
+            int address = writeSingleCoilRequest.getAddress();
+            assertThat(address, equalTo(1));
+            int value = writeSingleCoilRequest.getValue();
+            boolean coilValue = value == 0xFF00;
+            if (payloadClazzName.equals(Boolean.class.getSimpleName())) {
+                assertThat(coilValue, equalTo(true));
+            } else if (payloadClazzName.equals(Byte.class.getSimpleName())) {
+                assertThat(coilValue, equalTo(true));
+            } else if (payloadClazzName.equals(Short.class.getSimpleName())) {
+                assertThat(coilValue, equalTo(true));
+            } else if (payloadClazzName.equals(Calendar.class.getSimpleName())) {
+                assertThat(coilValue, equalTo(true));
+            } else if (payloadClazzName.equals(Float.class.getSimpleName())) {
+                assertThat(coilValue, equalTo(true));
+            } else if (payloadClazzName.equals(Integer.class.getSimpleName())) {
+                assertThat(coilValue, equalTo(true));
+            } else if (payloadClazzName.equals(String.class.getSimpleName())) {
+                assertThat(coilValue, equalTo(true));
+            }
+        } else if (modbusPdu instanceof WriteSingleRegisterRequest) {
+            // TODO: implement me
+        }
+    }
+
     @Test
-    public void coilEncoding() throws Exception {
-        byte[] bytes = (byte[]) MethodUtils.invokeMethod(new Plc4XModbusProtocol(), true, "produceCoilValue", Arrays.asList(false, false, true, false, false, false, true, false));
-        assertSame(1, bytes.length);
-        String s1 = String.format("%8s", Integer.toBinaryString(bytes[0] & 0xFF)).replace(' ', '0');
-        assertEquals("00100010", s1);
-
-        // TODO: just for reference we could use a bitset too
-        BitSet bitSet = new BitSet(8);
-        bitSet.set(1);
-        bitSet.set(5);
-        byte[] bytes1 = bitSet.toByteArray();
-        assertSame(1, bytes1.length);
-
-        String s2 = String.format("%8s", Integer.toBinaryString(bytes1[0] & 0xFF)).replace(' ', '0');
-        assertEquals("00100010", s2);
+    public void decode() throws Exception {
+        ArrayList<Object> in = new ArrayList<>();
+        SUT.encode(null, plcRequestContainer, in);
+        assertThat(in, hasSize(1));
+        syncInvoiceId();
+        ArrayList<Object> out = new ArrayList<>();
+        SUT.decode(null, modbusTcpPayload, out);
+        assertThat(out, hasSize(0));
+        LOGGER.info("PlcRequestContainer {}", plcRequestContainer);
+        PlcResponse plcResponse = plcRequestContainer.getResponseFuture().get();
+        ResponseItem responseItem = (ResponseItem) plcResponse.getResponseItem().get();
+        LOGGER.info("ResponseItem {}", responseItem);
+        ModbusPdu modbusPdu = modbusTcpPayload.getModbusPdu();
+        if (modbusPdu instanceof MaskWriteRegisterRequest) {
+            // TODO: implement me
+        } else if (modbusPdu instanceof ReadCoilsRequest) {
+            ReadResponseItem readResponseItem = (ReadResponseItem) responseItem;
+            Object value = readResponseItem.getValues().get(0);
+            if (payloadClazzName.equals(Boolean.class.getSimpleName())) {
+                assertThat(value, equalTo(Boolean.TRUE));
+            } else if (payloadClazzName.equals(Byte.class.getSimpleName())) {
+                assertThat(value, equalTo(Byte.valueOf("1")));
+            } else if (payloadClazzName.equals(Short.class.getSimpleName())) {
+                assertThat(value, equalTo(Short.valueOf("1")));
+            } else if (payloadClazzName.equals(Calendar.class.getSimpleName())) {
+                assertThat(value, equalTo(calenderInstance));
+            } else if (payloadClazzName.equals(Float.class.getSimpleName())) {
+                assertThat(value, equalTo(Float.valueOf("1")));
+            } else if (payloadClazzName.equals(Integer.class.getSimpleName())) {
+                assertThat(value, equalTo(Integer.valueOf("1")));
+            } else if (payloadClazzName.equals(String.class.getSimpleName())) {
+                assertThat(value, equalTo(String.valueOf("Hello World!")));
+            }
+        } else if (modbusPdu instanceof ReadDiscreteInputsRequest) {
+            // TODO: implement me
+        } else if (modbusPdu instanceof ReadHoldingRegistersRequest) {
+            // TODO: implement me
+        } else if (modbusPdu instanceof ReadInputRegistersRequest) {
+            // TODO: implement me
+        } else if (modbusPdu instanceof WriteMultipleCoilsRequest) {
+            // TODO: implement me
+        } else if (modbusPdu instanceof WriteMultipleRegistersRequest) {
+            // TODO: implement me
+        } else if (modbusPdu instanceof WriteSingleCoilResponse) {
+            // TODO: implement me
+        } else if (modbusPdu instanceof WriteSingleRegisterRequest) {
+            // TODO: implement me
+        }
+    }
+
+    private void syncInvoiceId() throws Exception {
+        Field transactionId = SUT.getClass().getDeclaredField("transactionId");
+        transactionId.setAccessible(true);
+        AtomicInteger correlationBuilder = (AtomicInteger) transactionId.get(SUT);
+
+        Field invokeIdField = ModbusTcpPayload.class.getDeclaredField("transactionId");
+        Field modifiersField = Field.class.getDeclaredField("modifiers");
+        modifiersField.setAccessible(true);
+        modifiersField.setInt(invokeIdField, invokeIdField.getModifiers() & ~Modifier.FINAL);
+        invokeIdField.setAccessible(true);
+        invokeIdField.set(modbusTcpPayload, (short) (correlationBuilder.get() - 1));
     }
 }
\ No newline at end of file