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 2018/10/04 07:17:38 UTC

[incubator-plc4x] 01/02: - Added some poc able to partially decode Delta-V packets

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

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

commit adf1f2ea686d598bac5c9113d2aaf6e06a113439
Author: Christofer Dutz <ch...@c-ware.de>
AuthorDate: Thu Oct 4 09:17:01 2018 +0200

    - Added some poc able to partially decode Delta-V packets
---
 plc4j/protocols/delta-v/pom.xml                    |  61 +++
 .../java/org/apache/plc4x/java/deltav/PoC.java     | 462 +++++++++++++++++++++
 plc4j/protocols/pom.xml                            |   1 +
 3 files changed, 524 insertions(+)

diff --git a/plc4j/protocols/delta-v/pom.xml b/plc4j/protocols/delta-v/pom.xml
new file mode 100644
index 0000000..77375c2
--- /dev/null
+++ b/plc4j/protocols/delta-v/pom.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one
+  or more contributor license agreements.  See the NOTICE file
+  distributed with this work for additional information
+  regarding copyright ownership.  The ASF licenses this file
+  to you under the Apache License, Version 2.0 (the
+  "License"); you may not use this file except in compliance
+  with the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing,
+  software distributed under the License is distributed on an
+  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+  KIND, either express or implied.  See the License for the
+  specific language governing permissions and limitations
+  under the License.
+  -->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <groupId>org.apache.plc4x</groupId>
+    <artifactId>plc4j-protocols</artifactId>
+    <version>0.2.0-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>plc4j-protocol-delta-v</artifactId>
+  <name>PLC4J: Protocol: Delta-V</name>
+  <description>Implementation of a PLC4X driver able to speak with Emerson devices using the DeltaV protocol.
+  </description>
+
+  <dependencies>
+    <dependency>
+      <groupId>org.pcap4j</groupId>
+      <artifactId>pcap4j-core</artifactId>
+      <version>1.7.3</version>
+    </dependency>
+    <dependency>
+      <groupId>org.pcap4j</groupId>
+      <artifactId>pcap4j-packetfactory-static</artifactId>
+      <version>1.7.3</version>
+      <scope>runtime</scope>
+    </dependency>
+
+    <dependency>
+      <groupId>io.netty</groupId>
+      <artifactId>netty-buffer</artifactId>
+      <version>4.1.23.Final</version>
+    </dependency>
+
+    <dependency>
+      <groupId>commons-codec</groupId>
+      <artifactId>commons-codec</artifactId>
+      <version>1.11</version>
+    </dependency>
+  </dependencies>
+
+</project>
\ No newline at end of file
diff --git a/plc4j/protocols/delta-v/src/test/java/org/apache/plc4x/java/deltav/PoC.java b/plc4j/protocols/delta-v/src/test/java/org/apache/plc4x/java/deltav/PoC.java
new file mode 100644
index 0000000..1229286
--- /dev/null
+++ b/plc4j/protocols/delta-v/src/test/java/org/apache/plc4x/java/deltav/PoC.java
@@ -0,0 +1,462 @@
+/*
+ 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.deltav;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import org.apache.commons.codec.binary.Hex;
+import org.pcap4j.core.*;
+import org.pcap4j.packet.UdpPacket;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.nio.ByteBuffer;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.StringTokenizer;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+public class PoC {
+
+    private static final Logger valueLogger = LoggerFactory.getLogger(PoC.class);
+
+    private static final int SNAPLEN = 65536;
+    private static final int READ_TIMEOUT = 10;
+
+    private PcapHandle receiveHandle;
+
+    private PoC() throws Exception {
+        PcapNetworkInterface nif = null;
+        for (PcapNetworkInterface dev : Pcaps.findAllDevs()) {
+            if("en7".equals(dev.getName())) {
+                nif = dev;
+                break;
+            }
+        }
+
+        if(nif == null) {
+            throw new RuntimeException("Couldn't find network device");
+        }
+
+        // Setup receiving of packets and redirecting them to the corresponding listeners.
+        // Filter packets to contain only the ip protocol number of the current protocol.
+        receiveHandle = nif.openLive(SNAPLEN, PcapNetworkInterface.PromiscuousMode.PROMISCUOUS, READ_TIMEOUT);
+
+        // Set the filter.
+        String filterString = "udp port 18507";
+        receiveHandle.setFilter(filterString, BpfProgram.BpfCompileMode.OPTIMIZE);
+
+        Map<String, Object> values = new HashMap<>();
+
+        byte[] timeBytes = ByteBuffer.allocate(8).putLong(System.currentTimeMillis()).array();
+        System.out.println("Current Time: " + Hex.encodeHexString(timeBytes));
+
+        PacketListener packetListener = packet -> {
+            try {
+                UdpPacket udpPacket = (UdpPacket) packet.getPayload().getPayload();
+                ByteBuf dis = Unpooled.wrappedBuffer(udpPacket.getPayload().getRawData());
+                //DataInputStream dis = new DataInputStream(new ByteArrayInputStream(udpPacket.getPayload().getRawData()));
+                dis.skipBytes(4);
+                short messageType = dis.readShort();
+                // We're only interested in type 2 messages.
+                if(messageType == 0x0002) {
+                    dis.skipBytes(10);
+                    short payloadType = dis.readShort();
+                    if(payloadType == 0x0403) {
+                        System.out.println("----------------------------------------------------------------------------------------");
+//                        System.out.println(Hex.encodeHexString(udpPacket.getPayload().getRawData()).replaceAll("(.{2})", "$1 ").replaceAll("(.{48})", "$1\n"));
+//                        System.out.println("----------------------");
+                        // Skip the rest of the header.
+                        dis.skipBytes(39);
+                        int endOfLastBlock = dis.readerIndex();
+                        int lastBlockSize = 0;
+                        short currentContext = 0;
+                        for(byte code = dis.readByte(); dis.readableBytes() > 2; code = dis.readByte()) {
+                            short blockId = dis.readShort();
+                            byte type = dis.readByte();
+
+                            // First check the code of the next block ...
+                            switch (code) {
+                                case (byte) 0x01: {
+                                    switch (type) {
+                                        case (byte) 0x01: {
+                                            // - It seems that the ids of a variable seem to occur multiple times
+                                            // - Also does it seem that this type of block sets some sort of context for following blocks
+                                            // - After setting up a machine with a new OS, the type of every of these is 0x00
+
+                                            // Found blocks:
+                                            // 01 00 23 01 1a 04 32 1c fd (size 7)
+                                            currentContext = blockId;
+                                            dis.skipBytes(5);
+                                            outputDetectedBlock("-- Switch Context --", dis, endOfLastBlock);
+                                            break;
+                                        }
+                                        case (byte) 0x00: {
+                                            // Is seems this simply signals the end of a packet.
+                                            currentContext = blockId;
+                                            dis.skipBytes(5);
+                                            outputDetectedBlock("-- Switch Context --", dis, endOfLastBlock);
+                                            break;
+                                        }
+                                        default: {
+                                            dumpAndExit(dis, endOfLastBlock, lastBlockSize, "Unexpected 0x01 type code: " + Hex.encodeHexString(new byte[]{type}));
+                                        }
+                                    }
+                                    break;
+                                }
+                                case (byte) 0x02: {
+                                    // Now inspect the block content ...
+                                    switch (type) {
+                                        case (byte) 0x01: {
+                                            // Possibly boolean value?
+                                            dis.skipBytes(1);
+                                            outputDetectedBlock("BOOL value", dis, endOfLastBlock);
+                                            break;
+                                        }
+                                        case (byte) 0x03: {
+                                            dis.skipBytes(5);
+                                            outputDetectedBlock("Unknown", dis, endOfLastBlock);
+                                            break;
+                                        }
+                                        case (byte) 0x05: {
+                                            // NOTE:
+                                            // - Each packet seems to have one of these
+                                            // - For each following packet the content is identical
+                                            // Found Block:
+                                            // 02 00 0c 05: 00 02 00 13 63 00 00 69 9c 1a
+                                            // 02 00 0c 05: 00 01 00 47 00 64 04 2a 17 53
+                                            dis.skipBytes(10);
+                                            outputDetectedBlock("Unknown", dis, endOfLastBlock);
+                                            break;
+                                        }
+                                        case (byte) 0x06: {
+                                            // Possibly Parse 16 bit int?
+                                            String id = "(U)INT-" + currentContext + "-" + blockId;
+                                            short shortValue = dis.readShort();
+                                            outputDetectedBlock("(U)INT value", dis, endOfLastBlock);
+                                            break;
+                                        }
+                                        case (byte) 0x07: {
+                                            // Possibly Parse 32 bit int?
+                                            String id = "(U)DINT-" + currentContext + "-" + blockId;
+                                            int intValue = dis.readInt();
+                                            outputDetectedBlock("(U)DINT value", dis, endOfLastBlock);
+                                            break;
+                                        }
+                                        case (byte) 0x08: {
+                                            // Parse float
+                                            String id = "REAL-" + currentContext + "-" + blockId;
+                                            float floatValue = dis.readFloat();
+                                            outputDetectedBlock("REAL value", dis, endOfLastBlock);
+                                            floatValue = Math.round(floatValue * 100.0f) / 100.0f;
+                                            if (!values.containsKey(id)) {
+                                                valueLogger.info(String.format("Variable with id: %s set to: %f", id, floatValue));
+                                                values.put(id, floatValue);
+                                            } else if (!values.get(id).equals(floatValue)) {
+                                                float oldValue = (float) values.get(id);
+                                                valueLogger.info(String.format("Variable with id: %s changed from: %f to: %f", id, oldValue, floatValue));
+                                                values.put(id, floatValue);
+                                            }
+                                            break;
+                                        }
+                                        case (byte) 0x21: {
+                                            // From having a look at the byte values these could be 32bit floating point values with some sort of parameters
+                                            String id = "REAL(P)-" + currentContext + "-" + blockId;
+                                            byte param = dis.readByte();
+                                            decodeParam(param);
+                                            float floatValue = dis.readFloat();
+                                            outputDetectedBlock("REAL(P) value", dis, endOfLastBlock);
+                                            floatValue = Math.round(floatValue * 100.0f) / 100.0f;
+                                            if (!values.containsKey(id)) {
+                                                valueLogger.info(String.format("Variable with id: %s set to: %f with params %s", id, floatValue, Hex.encodeHexString(new byte[]{param})));
+                                                values.put(id, floatValue);
+                                            } else if (!values.get(id).equals(floatValue)) {
+                                                float oldValue = (float) values.get(id);
+                                                valueLogger.info(String.format("Variable with id: %s changed from: %f to: %f with params %s", id, oldValue, floatValue, Hex.encodeHexString(new byte[]{param})));
+                                                values.put(id, floatValue);
+                                            }
+                                            break;
+                                        }
+                                        case (byte) 0x22: {
+                                            // Parse boolean (From what I learnt, this could be a flagged boolean, where the first byte is some sort of param)
+                                            String id = "BOOL(P)-" + currentContext + "-" + blockId;
+                                            byte param = dis.readByte();
+                                            decodeParam(param);
+                                            byte booleanByteValue = dis.readByte();
+                                            outputDetectedBlock("BOOL(P) value", dis, endOfLastBlock);
+                                            boolean booleanValue = false;
+                                            switch (booleanByteValue) {
+                                                case (byte) 0x00:
+                                                    booleanValue = false;
+                                                    break;
+                                                case (byte) 0x01:
+                                                    booleanValue = true;
+                                                    break;
+                                                default:
+                                                    System.out.println("Unknown second byte for boolean value 0x" + Hex.encodeHexString(new byte[]{booleanByteValue}));
+                                            }
+                                            if (!values.containsKey(id)) {
+                                                valueLogger.info(String.format("Variable with id: %s set to: %b with params %s", id, booleanValue, Hex.encodeHexString(new byte[]{param})));
+                                                values.put(id, booleanValue);
+                                            } else if (!values.get(id).equals(booleanValue)) {
+                                                boolean oldValue = (boolean) values.get(id);
+                                                valueLogger.info(String.format("Variable with id: %s changed from: %b to: %b with params %s", id, oldValue, booleanValue, Hex.encodeHexString(new byte[]{param})));
+                                                values.put(id, booleanValue);
+                                            }
+                                            break;
+                                        }
+                                        case (byte) 0x24: {
+                                            // No idea what this type is.
+                                            // NOTE:
+                                            // - It seems that the last byte seems to mirror the id of the block (Maybe the field id is just one byte and not a short)
+                                            // - It seems that these blocks are contained in every packet.
+                                            byte[] tmp = new byte[13]; // Has to be 13 in case of 0x0201 but some times 12
+                                            dis.readBytes(tmp);
+                                            outputDetectedBlock("Unknown", dis, endOfLastBlock);
+                                            break;
+                                        }
+                                        case (byte) 0x25: {
+                                            dis.skipBytes(6);
+                                            outputDetectedBlock("Unknown", dis, endOfLastBlock);
+                                            break;
+                                        }
+                                        case (byte) 0x47: {
+                                            // No idea what this type is.
+                                            // NOTE:
+                                            // - Seems to be sent as soon as a user confirms an alarm.
+                                            // - Seems the length is variable
+                                            // - Seems content is terminated by a "0x0000" value
+                                            // - All content seems to be encoded as short values with the first byte set to "0x00".
+                                            //
+                                            // Found Blocks:
+                                            // 00 4b 00 22 00 49 00 6e 00 69 00 74 00 69 00 61
+                                            // 00 6c 00 69 00 73 00 69 00 65 00 72 00 75 00 6e
+                                            // 00 67 00 20 00 2e 00 2e 00 2e 00 2e 00 2e 00 20
+                                            // 00 62 00 69 00 74 00 74 00 65 00 20 00 77 00 61
+                                            // 00 72 00 74 00 65 00 6e 00 00 02 00 67 47 00 1d
+                                            // 00 0b 00 57 00 41 00 52 00 54 00 45 00 4e 00 20
+                                            // 00 2e 00 2e 00 2e 00 20 00 00
+                                            short val = dis.readShort();
+                                            while(val != 0x0000) {
+                                                val = dis.readShort();
+                                            }
+                                            outputDetectedBlock("Unknown", dis, endOfLastBlock);
+                                            break;
+                                        }
+                                        case (byte) 0x48: {
+                                            // No idea what this type is.
+                                            // NOTE:
+                                            // - Seems to be sent as soon as an alarm is fired, changed or removed from the controller.
+                                            // - There seem to be only two types of values: 0x8000 and 0x8001
+                                            byte[] tmp = new byte[2];
+                                            dis.readBytes(tmp);
+                                            outputDetectedBlock("Unknown", dis, endOfLastBlock);
+                                            break;
+                                        }
+                                        case (byte) 0x49: {
+                                            // - Judging from the 0x80 first byte I would assume this is again one of these parametrized values
+                                            // - Would suggest this is a 32 bit integer value.
+                                            // Found blocks:
+                                            // 80 00 00 06 0d
+                                            String id = "(U)DINT(P)-" + currentContext + "-" + blockId;
+                                            byte param = dis.readByte();
+                                            decodeParam(param);
+                                            int intValue = dis.readInt();
+                                            if (!values.containsKey(id)) {
+                                                valueLogger.info(String.format("Variable with id: %s set to: %d with params %s", id, intValue, Hex.encodeHexString(new byte[]{param})));
+                                                values.put(id, intValue);
+                                            } else if (!values.get(id).equals(intValue)) {
+                                                int oldValue = (int) values.get(id);
+                                                valueLogger.info(String.format("Variable with id: %s changed from: %d to: %d with params %s", id, oldValue, intValue, Hex.encodeHexString(new byte[]{param})));
+                                                values.put(id, intValue);
+                                            }
+                                            outputDetectedBlock("(U)DINT(P) value", dis, endOfLastBlock);
+                                            break;
+                                        }
+                                        case (byte) 0x5B: {
+                                            // No idea what this type is.
+                                            dis.readShort();
+                                            outputDetectedBlock("Unknown", dis, endOfLastBlock);
+                                        }
+                                        case (byte) 0x63: {
+                                            // No idea what this type is.
+                                            // NOTE:
+                                            // - It seems that this block is contained in every packet exactly once for id 5
+                                            // Found blocks:
+                                            // 02 00 06 63: 64 00 19 b9 88
+                                            byte[] tmp = new byte[5];
+                                            dis.readBytes(tmp);
+//                                            System.out.println(String.format("Got 0x63 type for id %s with content: %s", blockId, Hex.encodeHexString(tmp)));
+                                            outputDetectedBlock("Unknown", dis, endOfLastBlock);
+                                            break;
+                                        }
+                                        case (byte) 0x75: {
+                                            // No idea what this type is.
+                                            // NOTE:
+                                            // - Exactly 3 blocks of this type with extremely similar content is being sent every 60 seconds for the ids: 17, 16 and 34
+                                            //                            001600280d0100000000280015f360000000000100
+                                            //                            001600280d0100000000280015f360000000000100
+                                            int size = "001600280d0100000000280015f360000000000100".length() / 2; //21
+                                            byte[] tmp = new byte[size];
+                                            dis.readBytes(tmp);
+//                                            System.out.println(String.format("Got 0x75 type for id %s with content: %s", blockId, Hex.encodeHexString(tmp)));
+                                            outputDetectedBlock("Unknown", dis, endOfLastBlock);
+                                            break;
+                                        }
+                                        case (byte) 0x76: {
+                                            // No idea what this type is.
+                                            // These strange blocks containing a repeating pattern of 0x00 and 0xFF
+                                            // NOTE:
+                                            // - These blocks seem to be transferred whenever a boolean value is changed.
+                                            // - There seem to be two variants:
+                                            //   - Variant 1 (shorter) is transferred as soon as a boolean value is set
+                                            //   - Variant 2 (longer) is transferred as soon as a boolean values is unset
+                                            // - Variant always looks the same no matter what combination of boolean values is set
+                                            // - The blocks always refer to ids 0, 1 and 2
+                                            // - The additional part of Variant 2 always starts with:
+                                            //   "000700420049004e005f0041004c004d000000180018000300002ae7"
+                                            //   The last 4 bytes (maybe more) seem to be an always increasing value
+                                            //   (Maybe some sort of timestamp)
+                                            short length = (short) (dis.readShort() - 3);
+                                            byte[] tmp = new byte[length];
+                                            dis.readBytes(tmp);
+                                            String hexBlock = Hex.encodeHexString(tmp).replaceAll("(.{32})", "$1\n");
+//                                            System.out.println(String.format("Got 0x76 type for id %s with content: \n%s", blockId, hexBlock));
+                                            outputDetectedBlock("Unknown", dis, endOfLastBlock);
+                                            break;
+                                        }
+                                        case (byte) 0xF6: {
+                                            // Only seen in 0x0102 blocks
+                                            dis.skipBytes(4);
+                                            outputDetectedBlock("Unknown", dis, endOfLastBlock);
+                                            break;
+                                        }
+                                        default: {
+                                            dumpAndExit(dis, endOfLastBlock, lastBlockSize, "Unexpected 0x02 type code: " + Hex.encodeHexString(new byte[]{type}));
+                                            /*if(code == (byte) 0x01) {
+                                                dis.skipBytes(4);
+                                            } else {
+                                                dumpAndExit(dis, endOfLastBlock, lastBlockSize, "Unknown variable type 0x" + Hex.encodeHexString(new byte[]{type}));
+                                            }
+                                            outputDetectedBlock("Unknown", dis, endOfLastBlock);*/
+                                        }
+
+                                    }
+                                    break;
+                                }
+                                case (byte) 0x03: {
+                                    // TODO: Check if these other types still exist ..
+                                    // Found blocks:
+                                    // 03 00 23 00 00 00 4a             (size 6)
+                                    // 03 01 00 27 01 1e 04 36 1d       (size 8)
+                                    // 03 01 00 24 01 1b 04 33 1c fe    (size 9)
+                                    switch (type) {
+                                        case (byte) 0x00: {
+                                            dis.skipBytes(3);
+                                            break;
+                                        }
+                                        default: {
+                                            dumpAndExit(dis, endOfLastBlock, lastBlockSize, "Unexpected 0x03 type code: " + Hex.encodeHexString(new byte[]{type}));
+                                        }
+                                    }
+                                    break;
+                                }
+                                default: {
+                                    dumpAndExit(dis, endOfLastBlock, lastBlockSize, "Unexpected code: " + Hex.encodeHexString(new byte[]{code}));
+                                }
+                            }
+                            lastBlockSize = dis.readerIndex() - endOfLastBlock;
+                            endOfLastBlock = dis.readerIndex();
+                        }
+
+                    }
+                }
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        };
+
+        ExecutorService pool = Executors.newScheduledThreadPool(2);
+        pool.execute(() -> {
+            try {
+                receiveHandle.loop(-1, packetListener);
+            } catch (PcapNativeException | InterruptedException | NotOpenException e) {
+                e.printStackTrace();
+            }
+        });
+    }
+
+    protected void outputDetectedBlock(String name, ByteBuf byteBuf, int endOfLastBlock) {
+        int blockSize = byteBuf.readerIndex() - endOfLastBlock;
+        byte[] blockContent = new byte[blockSize];
+        byteBuf.getBytes(endOfLastBlock, blockContent);
+        String content = "   " + Hex.encodeHexString(blockContent).replaceAll("(.{2})", "$1 ");
+        System.out.println(String.format("Block: %20s %s", name, content));
+    }
+
+    protected void dumpAndExit(ByteBuf byteBuf, int endOfLastBlock, int lastBlockSize, String message) {
+        int errorPos = byteBuf.readerIndex();
+        int lastBlockStart = errorPos - endOfLastBlock;
+        byteBuf.resetReaderIndex();
+        System.out.println("-------------------- ERROR --------------------");
+        String packetAsHexString = Hex.encodeHexString(byteBuf.array()).replaceAll("(.{2})", "$1 ").replaceAll("(.{48})", "$1\n");
+        StringTokenizer stringTokenizer = new StringTokenizer(packetAsHexString, "\n");
+        while (stringTokenizer.hasMoreElements()) {
+            String line = stringTokenizer.nextToken();
+            System.out.println(line);
+            if((errorPos < 16) && (errorPos >= 0)) {
+                StringBuffer sb = new StringBuffer();
+                for(int i = 0; i < errorPos - 1; i++) {
+                    sb.append("---");
+                }
+                sb.append("^");
+                System.out.println(sb);
+                System.out.println("Last block started: " + lastBlockStart + " bytes before error and had a size of: " + lastBlockSize);
+                System.out.println(message);
+                System.out.println("\n");
+            }
+            errorPos -= 16;
+        }
+        throw new RuntimeException("Error");
+    }
+
+    // These seem to be the values decoded for parametrized values ...
+    private void decodeParam(byte param) {
+        switch (param) {
+            case (byte) 0x00: // 00000000
+            case (byte) 0x88: // 10001000
+            case (byte) 0x84: // 10000100
+            case (byte) 0xC3: // 11000011
+            case (byte) 0x0C: // 00001100
+            case (byte) 0x80: // 10000000
+            case (byte) 0xC0: // 11000000
+                break;
+            default:
+                throw new RuntimeException("Unexpected param value " + param);
+        }
+
+    }
+
+    public static void main(String[] args) throws Exception {
+        new PoC();
+    }
+
+}
diff --git a/plc4j/protocols/pom.xml b/plc4j/protocols/pom.xml
index 636a60f..1753c6c 100644
--- a/plc4j/protocols/pom.xml
+++ b/plc4j/protocols/pom.xml
@@ -37,6 +37,7 @@
     <module>driver-bases</module>
 
     <module>ads</module>
+    <module>delta-v</module>
     <module>ethernetip</module>
     <module>modbus</module>
     <module>s7</module>