You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@plc4x.apache.org by ld...@apache.org on 2020/10/21 08:43:21 UTC

[plc4x] 21/21: Support for NMT and HEARTBEAT subscriptions.

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

ldywicki pushed a commit to branch feature/socketcan
in repository https://gitbox.apache.org/repos/asf/plc4x.git

commit 94fb0cb16e68c81c2ce7f62aa53eb159fbbcd150
Author: Ɓukasz Dywicki <lu...@code-house.org>
AuthorDate: Wed Oct 21 10:42:11 2020 +0200

    Support for NMT and HEARTBEAT subscriptions.
---
 sandbox/test-java-can-driver/pom.xml               |  13 +
 .../api/segmentation/accumulator/ByteStorage.java  |  18 ++
 .../apache/plc4x/java/can/field/CANOpenField.java  |   4 +
 ...penNMTField.java => CANOpenHeartbeatField.java} |  18 +-
 .../plc4x/java/can/field/CANOpenNMTField.java      |  10 +-
 .../plc4x/java/can/field/CANOpenPDOField.java      |   8 +-
 .../java/can/field/CANOpenSubscriptionField.java   |  11 +
 .../java/can/protocol/CANOpenProtocolLogic.java    |  51 +++-
 ...nHandle.java => CANOpenSubscriptionHandle.java} |  13 +-
 .../plc4x/java/can/field/CANOpenNMTFieldTest.java  |   9 +-
 .../plc4x/java/can/field/CANOpenSDOFieldTest.java  |  26 ++
 .../resources/testsuite/CANOpenDriverSDOIT.xml     | 324 +++++++++++++++++++++
 12 files changed, 472 insertions(+), 33 deletions(-)

diff --git a/sandbox/test-java-can-driver/pom.xml b/sandbox/test-java-can-driver/pom.xml
index 4ed0eaa..fcb4507 100644
--- a/sandbox/test-java-can-driver/pom.xml
+++ b/sandbox/test-java-can-driver/pom.xml
@@ -29,6 +29,7 @@
   <artifactId>test-java-can-driver</artifactId>
 
   <name>Sandbox: Test Generated CAN Driver</name>
+  <packaging>bundle</packaging>
 
   <build>
     <plugins>
@@ -62,6 +63,18 @@
           </execution>
         </executions>
       </plugin>
+      <plugin>
+        <groupId>org.apache.felix</groupId>
+        <artifactId>maven-bundle-plugin</artifactId>
+        <extensions>true</extensions>
+        <configuration>
+          <instructions>
+            <Bundle-SymbolicName>${project.groupId}.${project.artifactId}</Bundle-SymbolicName>
+            <Bundle-Activator>org.apache.plc4x.java.osgi.DriverActivator</Bundle-Activator>
+            <Export-Service>org.apache.plc4x.java.api.PlcDriver,org.apache.plc4x.java.can.CANOpenPlcDriver</Export-Service>
+          </instructions>
+        </configuration>
+      </plugin>
     </plugins>
   </build>
 
diff --git a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/api/segmentation/accumulator/ByteStorage.java b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/api/segmentation/accumulator/ByteStorage.java
index 1f143f2..ae0a6b4 100644
--- a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/api/segmentation/accumulator/ByteStorage.java
+++ b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/api/segmentation/accumulator/ByteStorage.java
@@ -56,4 +56,22 @@ public class ByteStorage<T> implements Storage<T, byte[]> {
         }
     }
 
+    public static class SDODownloadStorage extends ByteStorage<SDORequest> {
+        public SDODownloadStorage() {
+            super((sdoRequest -> {
+                if (sdoRequest instanceof SDOSegmentDownloadRequest) {
+                    return ((SDOSegmentDownloadRequest) sdoRequest).getData();
+                }
+                if (sdoRequest instanceof  SDOInitiateDownloadRequest) {
+                    SDOInitiateDownloadRequest initiate = (SDOInitiateDownloadRequest) sdoRequest;
+
+                    if (initiate.getPayload() instanceof SDOInitiateExpeditedUploadResponse) {
+                        return ((SDOInitiateExpeditedUploadResponse) initiate.getPayload()).getData();
+                    }
+                }
+                return new byte[0];
+            }));
+        }
+    }
+
 }
diff --git a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/field/CANOpenField.java b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/field/CANOpenField.java
index 6917351..ddd2a1a 100644
--- a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/field/CANOpenField.java
+++ b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/field/CANOpenField.java
@@ -46,6 +46,10 @@ public abstract class CANOpenField implements PlcField {
             return CANOpenSDOField.of(addressString);
         } else if (CANOpenPDOField.matches(addressString)) {
             return CANOpenPDOField.of(addressString);
+        } else if (CANOpenNMTField.matches(addressString)) {
+            return CANOpenNMTField.of(addressString);
+        } else if (CANOpenHeartbeatField.matches(addressString)) {
+            return CANOpenHeartbeatField.of(addressString);
         }
 
         throw new PlcInvalidFieldException("Unable to parse address: " + addressString);
diff --git a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/field/CANOpenNMTField.java b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/field/CANOpenHeartbeatField.java
similarity index 73%
copy from sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/field/CANOpenNMTField.java
copy to sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/field/CANOpenHeartbeatField.java
index 83bba15..ef6a4ad 100644
--- a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/field/CANOpenNMTField.java
+++ b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/field/CANOpenHeartbeatField.java
@@ -19,18 +19,24 @@ under the License.
 package org.apache.plc4x.java.can.field;
 
 import org.apache.plc4x.java.api.exceptions.PlcInvalidFieldException;
+import org.apache.plc4x.java.canopen.readwrite.types.CANOpenService;
 
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-public class CANOpenNMTField extends CANOpenField {
+public class CANOpenHeartbeatField extends CANOpenField implements CANOpenSubscriptionField {
 
-    public static final Pattern ADDRESS_PATTERN = Pattern.compile("NMT|NMT:" + CANOpenField.NODE_PATTERN);
+    public static final Pattern ADDRESS_PATTERN = Pattern.compile("HEARTBEAT|HEARTBEAT:" + CANOpenField.NODE_PATTERN);
 
-    public CANOpenNMTField(int node) {
+    public CANOpenHeartbeatField(int node) {
         super(node);
     }
 
+    @Override
+    public CANOpenService getService() {
+        return CANOpenService.HEARTBEAT;
+    }
+
     public boolean isWildcard() {
         return getNodeId() == 0;
     }
@@ -48,11 +54,11 @@ public class CANOpenNMTField extends CANOpenField {
         throw new PlcInvalidFieldException(addressString, ADDRESS_PATTERN);
     }
 
-    public static CANOpenNMTField of(String addressString) {
+    public static CANOpenHeartbeatField of(String addressString) {
         Matcher matcher = getMatcher(addressString);
-        int nodeId = Integer.parseInt(matcher.group("nodeId"));
+        int nodeId = matcher.group("nodeId") == null ? 0 : Integer.parseInt(matcher.group("nodeId"));
 
-        return new CANOpenNMTField(nodeId);
+        return new CANOpenHeartbeatField(nodeId);
     }
 
 }
diff --git a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/field/CANOpenNMTField.java b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/field/CANOpenNMTField.java
index 83bba15..74370b1 100644
--- a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/field/CANOpenNMTField.java
+++ b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/field/CANOpenNMTField.java
@@ -19,11 +19,12 @@ under the License.
 package org.apache.plc4x.java.can.field;
 
 import org.apache.plc4x.java.api.exceptions.PlcInvalidFieldException;
+import org.apache.plc4x.java.canopen.readwrite.types.CANOpenService;
 
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-public class CANOpenNMTField extends CANOpenField {
+public class CANOpenNMTField extends CANOpenField implements CANOpenSubscriptionField {
 
     public static final Pattern ADDRESS_PATTERN = Pattern.compile("NMT|NMT:" + CANOpenField.NODE_PATTERN);
 
@@ -31,6 +32,11 @@ public class CANOpenNMTField extends CANOpenField {
         super(node);
     }
 
+    @Override
+    public CANOpenService getService() {
+        return CANOpenService.NMT;
+    }
+
     public boolean isWildcard() {
         return getNodeId() == 0;
     }
@@ -50,7 +56,7 @@ public class CANOpenNMTField extends CANOpenField {
 
     public static CANOpenNMTField of(String addressString) {
         Matcher matcher = getMatcher(addressString);
-        int nodeId = Integer.parseInt(matcher.group("nodeId"));
+        int nodeId = matcher.group("nodeId") == null ? 0 : Integer.parseInt(matcher.group("nodeId"));
 
         return new CANOpenNMTField(nodeId);
     }
diff --git a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/field/CANOpenPDOField.java b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/field/CANOpenPDOField.java
index 2761260..cfe050d 100644
--- a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/field/CANOpenPDOField.java
+++ b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/field/CANOpenPDOField.java
@@ -26,7 +26,7 @@ import java.util.Optional;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-public class CANOpenPDOField extends CANOpenField {
+public class CANOpenPDOField extends CANOpenField implements CANOpenSubscriptionField {
 
     public static final Pattern ADDRESS_PATTERN = Pattern.compile("(?<pdo>(?:RECEIVE|TRANSMIT)_PDO_[1-4]):" + CANOpenField.NODE_PATTERN + ":(?<canDataType>\\w+)(\\[(?<numberOfElements>\\d)])?");
     private final CANOpenService service;
@@ -74,4 +74,10 @@ public class CANOpenPDOField extends CANOpenField {
     public CANOpenService getService() {
         return service;
     }
+
+    @Override
+    public boolean isWildcard() {
+        return false;
+    }
+
 }
diff --git a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/field/CANOpenSubscriptionField.java b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/field/CANOpenSubscriptionField.java
new file mode 100644
index 0000000..78fafc0
--- /dev/null
+++ b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/field/CANOpenSubscriptionField.java
@@ -0,0 +1,11 @@
+package org.apache.plc4x.java.can.field;
+
+import org.apache.plc4x.java.canopen.readwrite.types.CANOpenService;
+
+public interface CANOpenSubscriptionField {
+    CANOpenService getService();
+
+    boolean isWildcard();
+
+    int getNodeId();
+}
diff --git a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/protocol/CANOpenProtocolLogic.java b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/protocol/CANOpenProtocolLogic.java
index 5285b16..a46b17d 100644
--- a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/protocol/CANOpenProtocolLogic.java
+++ b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/protocol/CANOpenProtocolLogic.java
@@ -38,16 +38,14 @@ import org.apache.plc4x.java.can.canopen.CANOpenFrameBuilderFactory;
 import org.apache.plc4x.java.can.canopen.socketcan.CANOpenSocketCANFrameBuilder;
 import org.apache.plc4x.java.can.configuration.CANConfiguration;
 import org.apache.plc4x.java.can.context.CANOpenDriverContext;
-import org.apache.plc4x.java.can.field.CANOpenField;
-import org.apache.plc4x.java.can.field.CANOpenNMTField;
-import org.apache.plc4x.java.can.field.CANOpenPDOField;
-import org.apache.plc4x.java.can.field.CANOpenSDOField;
+import org.apache.plc4x.java.can.field.*;
 import org.apache.plc4x.java.can.socketcan.SocketCANConversation;
 import org.apache.plc4x.java.canopen.readwrite.*;
 import org.apache.plc4x.java.canopen.readwrite.io.CANOpenPayloadIO;
 import org.apache.plc4x.java.canopen.readwrite.io.DataItemIO;
 import org.apache.plc4x.java.canopen.readwrite.types.CANOpenService;
 import org.apache.plc4x.java.canopen.readwrite.types.NMTState;
+import org.apache.plc4x.java.canopen.readwrite.types.NMTStateRequest;
 import org.apache.plc4x.java.spi.ConversationContext;
 import org.apache.plc4x.java.spi.Plc4xProtocolBase;
 import org.apache.plc4x.java.spi.configuration.HasConfiguration;
@@ -251,11 +249,15 @@ public class CANOpenProtocolLogic extends Plc4xProtocolBase<CANOpenFrame> implem
                 answers.put(entry.getKey(), new ResponseItem<>(PlcResponseCode.UNSUPPORTED, null));
             } else if ((subscription.getPlcField() instanceof CANOpenPDOField)) {
                 answers.put(entry.getKey(), new ResponseItem<>(PlcResponseCode.OK,
-                    new CANOpenPDOSubscriptionHandle(this, entry.getKey(), (CANOpenPDOField) subscription.getPlcField())
+                    new CANOpenSubscriptionHandle(this, entry.getKey(), (CANOpenPDOField) subscription.getPlcField())
                 ));
             } else if ((subscription.getPlcField() instanceof CANOpenNMTField)) {
                 answers.put(entry.getKey(), new ResponseItem<>(PlcResponseCode.OK,
-                    new CANOpenNMTSubscriptionHandle(this, entry.getKey(), (CANOpenNMTField) subscription.getPlcField())
+                    new CANOpenSubscriptionHandle(this, entry.getKey(), (CANOpenNMTField) subscription.getPlcField())
+                ));
+            } else if ((subscription.getPlcField() instanceof CANOpenHeartbeatField)) {
+                answers.put(entry.getKey(), new ResponseItem<>(PlcResponseCode.OK,
+                    new CANOpenSubscriptionHandle(this, entry.getKey(), (CANOpenHeartbeatField) subscription.getPlcField())
                 ));
             } else {
                 answers.put(entry.getKey(), new ResponseItem<>(PlcResponseCode.INVALID_ADDRESS, null));
@@ -294,11 +296,11 @@ public class CANOpenProtocolLogic extends Plc4xProtocolBase<CANOpenFrame> implem
         CANOpenService service = msg.getService();
         CANOpenPayload payload = msg.getPayload();
 
-        if (service != null) {
+        if (service != null && nodeId != this.configuration.getNodeId()) {
             if (service.getPdo() && payload instanceof CANOpenPDOPayload) {
-                publishEvent(service, nodeId, (CANOpenPDOPayload) payload);
-            } else if (service == CANOpenService.NMT && payload instanceof CANOpenNetworkPayload) {
-                publishEvent(service, nodeId, (CANOpenNetworkPayload) payload);
+                publishEvent(service, nodeId, payload);
+            } else if (service == CANOpenService.HEARTBEAT && payload instanceof CANOpenHeartbeatPayload) {
+                publishEvent(service, nodeId, payload);
             } else {
                 String hex = "";
                 if (logger.isInfoEnabled()) {
@@ -331,14 +333,14 @@ public class CANOpenProtocolLogic extends Plc4xProtocolBase<CANOpenFrame> implem
             Consumer<PlcSubscriptionEvent> consumer = entry.getValue();
 
             for (InternalPlcSubscriptionHandle handler : registration.getAssociatedHandles()) {
-                if (handler instanceof CANOpenPDOSubscriptionHandle && payload instanceof CANOpenPDOPayload) {
-                    CANOpenPDOSubscriptionHandle handle = (CANOpenPDOSubscriptionHandle) handler;
+                CANOpenSubscriptionHandle handle = (CANOpenSubscriptionHandle) handler;
+                if (payload instanceof CANOpenPDOPayload) {
 
                     if (handle.matches(service, nodeId)) {
                         logger.trace("Dispatching notification {} for node {} to {}", service, nodeId, handle);
                         dispatchedHandle = handle;
 
-                        CANOpenPDOField field = handle.getField();
+                        CANOpenPDOField field = (CANOpenPDOField) handle.getField();
                         byte[] data = ((CANOpenPDOPayload) payload).getPdo().getData();
                         try {
                             PlcValue value = DataItemIO.staticParse(new ReadBuffer(data, true), field.getCanOpenDataType(), data.length);
@@ -362,9 +364,7 @@ public class CANOpenProtocolLogic extends Plc4xProtocolBase<CANOpenFrame> implem
                             consumer.accept(event);
                         }
                     }
-                } else if (handler instanceof CANOpenPDOSubscriptionHandle && payload instanceof CANOpenHeartbeatPayload) {
-                    CANOpenNMTSubscriptionHandle handle = (CANOpenNMTSubscriptionHandle) handler;
-
+                } else if (payload instanceof CANOpenHeartbeatPayload) {
                     if (handle.matches(service, nodeId)) {
                         logger.trace("Dispatching notification {} for node {} to {}", service, nodeId, handle);
                         dispatchedHandle = handle;
@@ -383,6 +383,25 @@ public class CANOpenProtocolLogic extends Plc4xProtocolBase<CANOpenFrame> implem
                         );
                         consumer.accept(event);
                     }
+                } else if (payload instanceof CANOpenNetworkPayload) {
+                    if (handle.matches(service, nodeId)) {
+                        logger.trace("Dispatching notification {} for node {} to {}", service, nodeId, handle);
+                        dispatchedHandle = handle;
+
+                        final NMTStateRequest state = ((CANOpenNetworkPayload) payload).getRequest();
+                        Map<String, PlcValue> fields = new HashMap<>();
+                        fields.put("state", new PlcUSINT(state.getValue()));
+                        fields.put("node", new PlcUSINT(nodeId));
+                        PlcStruct struct = new PlcStruct(fields);
+                        DefaultPlcSubscriptionEvent event = new DefaultPlcSubscriptionEvent(
+                            Instant.now(),
+                            Collections.singletonMap(
+                                handle.getName(),
+                                new ResponseItem<>(PlcResponseCode.OK, struct)
+                            )
+                        );
+                        consumer.accept(event);
+                    }
                 }
             }
         }
diff --git a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/protocol/CANOpenPDOSubscriptionHandle.java b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/protocol/CANOpenSubscriptionHandle.java
similarity index 56%
rename from sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/protocol/CANOpenPDOSubscriptionHandle.java
rename to sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/protocol/CANOpenSubscriptionHandle.java
index 80876c6..e318da3 100644
--- a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/protocol/CANOpenPDOSubscriptionHandle.java
+++ b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/protocol/CANOpenSubscriptionHandle.java
@@ -1,15 +1,16 @@
 package org.apache.plc4x.java.can.protocol;
 
 import org.apache.plc4x.java.can.field.CANOpenPDOField;
+import org.apache.plc4x.java.can.field.CANOpenSubscriptionField;
 import org.apache.plc4x.java.canopen.readwrite.types.CANOpenService;
 import org.apache.plc4x.java.spi.messages.PlcSubscriber;
 import org.apache.plc4x.java.spi.model.DefaultPlcSubscriptionHandle;
 
-public class CANOpenPDOSubscriptionHandle extends DefaultPlcSubscriptionHandle {
+public class CANOpenSubscriptionHandle extends DefaultPlcSubscriptionHandle {
     private final String name;
-    private final CANOpenPDOField field;
+    private final CANOpenSubscriptionField field;
 
-    public CANOpenPDOSubscriptionHandle(PlcSubscriber subscriber, String name, CANOpenPDOField field) {
+    public CANOpenSubscriptionHandle(PlcSubscriber subscriber, String name, CANOpenSubscriptionField field) {
         super(subscriber);
         this.name = name;
         this.field = field;
@@ -19,19 +20,19 @@ public class CANOpenPDOSubscriptionHandle extends DefaultPlcSubscriptionHandle {
         if (field.getService() != service) {
             return false;
         }
-        return field.getNodeId() == identifier;
+        return field.isWildcard() || field.getNodeId() == identifier;
     }
 
     public String getName() {
         return name;
     }
 
-    public CANOpenPDOField getField() {
+    public CANOpenSubscriptionField getField() {
         return field;
     }
 
     public String toString() {
-        return "CANOpenPDOSubscriptionHandle [service=" + field.getService() + ", node=" + intAndHex(field.getNodeId()) + ", cob=" + intAndHex(field.getService().getMin() + field.getNodeId()) + "]";
+        return "CANopenSubscriptionHandle [service=" + field.getService() + ", node=" + intAndHex(field.getNodeId()) + ", cob=" + intAndHex(field.getService().getMin() + field.getNodeId()) + "]";
     }
 
     private static String intAndHex(int val) {
diff --git a/sandbox/test-java-can-driver/src/test/java/org/apache/plc4x/java/can/field/CANOpenNMTFieldTest.java b/sandbox/test-java-can-driver/src/test/java/org/apache/plc4x/java/can/field/CANOpenNMTFieldTest.java
index 3f48fc1..06db074 100644
--- a/sandbox/test-java-can-driver/src/test/java/org/apache/plc4x/java/can/field/CANOpenNMTFieldTest.java
+++ b/sandbox/test-java-can-driver/src/test/java/org/apache/plc4x/java/can/field/CANOpenNMTFieldTest.java
@@ -35,12 +35,17 @@ class CANOpenNMTFieldTest {
 
     @Test
     public void testWildcardSyntax() {
-        final CANOpenNMTField canField = CANOpenNMTField.of("NMT:0");
+        CANOpenNMTField canField = CANOpenNMTField.of("NMT:0");
 
         assertEquals(0, canField.getNodeId());
         assertTrue(canField.isWildcard());
-    }
 
+        // an simplified syntax
+        canField = CANOpenNMTField.of("NMT");
+
+        assertEquals(0, canField.getNodeId());
+        assertTrue(canField.isWildcard());
+    }
 
     @Test
     public void testInvalidSyntax() {
diff --git a/sandbox/test-java-can-driver/src/test/java/org/apache/plc4x/java/can/field/CANOpenSDOFieldTest.java b/sandbox/test-java-can-driver/src/test/java/org/apache/plc4x/java/can/field/CANOpenSDOFieldTest.java
new file mode 100644
index 0000000..ddecd2b
--- /dev/null
+++ b/sandbox/test-java-can-driver/src/test/java/org/apache/plc4x/java/can/field/CANOpenSDOFieldTest.java
@@ -0,0 +1,26 @@
+package org.apache.plc4x.java.can.field;
+
+import org.apache.plc4x.java.api.exceptions.PlcInvalidFieldException;
+import org.apache.plc4x.java.canopen.readwrite.types.CANOpenDataType;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class CANOpenSDOFieldTest {
+
+    @Test
+    public void testNodeSyntax() {
+        final CANOpenSDOField canField = CANOpenSDOField.of("SDO:20:0x10/0xAA:RECORD");
+
+        assertEquals(20, canField.getNodeId());
+        assertEquals(0x10, canField.getIndex());
+        assertEquals(0xAA, canField.getSubIndex());
+        assertEquals(CANOpenDataType.RECORD, canField.getCanOpenDataType());
+    }
+
+    @Test
+    public void testInvalidSyntax() {
+        assertThrows(PlcInvalidFieldException.class, () -> CANOpenSDOField.of("SDO:"));
+    }
+
+}
\ No newline at end of file
diff --git a/sandbox/test-java-can-driver/src/test/resources/testsuite/CANOpenDriverSDOIT.xml b/sandbox/test-java-can-driver/src/test/resources/testsuite/CANOpenDriverSDOIT.xml
new file mode 100644
index 0000000..190f3be
--- /dev/null
+++ b/sandbox/test-java-can-driver/src/test/resources/testsuite/CANOpenDriverSDOIT.xml
@@ -0,0 +1,324 @@
+<?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.
+  -->
+<test:driver-testsuite xmlns:test="https://plc4x.apache.org/schemas/driver-testsuite.xsd"
+                       bigEndian="false">
+
+  <name>CANOpen SDO Segmentation</name>
+
+  <driver-name>canopen</driver-name>
+  <driver-parameters>
+    <parameter>
+      <name>nodeId</name>
+      <value>15</value>
+    </parameter>
+  </driver-parameters>
+
+  <testcase>
+    <name>Expedited SDO read request</name>
+    <description>
+      Single field read request which answers with 4 bytes of data.
+    </description>
+    <steps>
+      <api-request name="Receive Read Request from application">
+        <TestReadRequest className="org.apache.plc4x.test.driver.model.api.TestReadRequest">
+          <fields>
+            <field className="org.apache.plc4x.test.driver.model.api.TestField">
+              <name>sdo1</name>
+              <address>SDO:1:1000/22:UNSIGNED32</address>
+            </field>
+          </fields>
+        </TestReadRequest>
+      </api-request>
+      <outgoing-plc-message name="Send SDO Initialize Upload Request">
+        <CANOpenSocketCANFrame className="org.apache.plc4x.java.can.canopen.socketcan.CANOpenSocketCANFrame">
+          <nodeId>1</nodeId>
+          <service>RECEIVE_SDO</service>
+          <payload className="org.apache.plc4x.java.canopen.readwrite.CANOpenSDORequest">
+            <command>INITIATE_UPLOAD</command>
+            <request className="org.apache.plc4x.java.canopen.readwrite.SDOInitiateUploadRequest">
+              <address className="org.apache.plc4x.java.canopen.readwrite.IndexAddress">
+                <index>1000</index>
+                <subindex>22</subindex>
+              </address>
+            </request>
+          </payload>
+        </CANOpenSocketCANFrame>
+      </outgoing-plc-message>
+      <incoming-plc-message name="Receive SDO Initialize Upload Response for other node">
+        <!-- one unwanted frame -->
+        <CANOpenSocketCANFrame className="org.apache.plc4x.java.can.canopen.socketcan.CANOpenSocketCANFrame">
+          <nodeId>1</nodeId>
+          <service>TRANSMIT_SDO</service>
+          <payload className="org.apache.plc4x.java.canopen.readwrite.CANOpenSDOResponse">
+            <command>INITIATE_UPLOAD</command>
+            <response className="org.apache.plc4x.java.canopen.readwrite.SDOInitiateUploadResponse">
+              <expedited>true</expedited>
+              <indicated>true</indicated>
+              <address className="org.apache.plc4x.java.canopen.readwrite.IndexAddress">
+                <index>1001</index>
+                <subindex>22</subindex>
+              </address>
+              <payload className="org.apache.plc4x.java.canopen.readwrite.SDOInitiateExpeditedUploadResponse">
+                <data>YXNkZg==</data>
+              </payload>
+            </response>
+          </payload>
+        </CANOpenSocketCANFrame>
+      </incoming-plc-message>
+      <incoming-plc-message name="Receive SDO Initialize Upload Response for from requested node">
+        <CANOpenSocketCANFrame className="org.apache.plc4x.java.can.canopen.socketcan.CANOpenSocketCANFrame">
+          <nodeId>1</nodeId>
+          <service>TRANSMIT_SDO</service>
+          <payload className="org.apache.plc4x.java.canopen.readwrite.CANOpenSDOResponse">
+            <command>INITIATE_UPLOAD</command>
+            <response className="org.apache.plc4x.java.canopen.readwrite.SDOInitiateUploadResponse">
+              <expedited>true</expedited>
+              <indicated>true</indicated>
+              <address className="org.apache.plc4x.java.canopen.readwrite.IndexAddress">
+                <index>1000</index>
+                <subindex>22</subindex>
+              </address>
+              <payload className="org.apache.plc4x.java.canopen.readwrite.SDOInitiateExpeditedUploadResponse">
+                <data>YXNkZg==</data>
+              </payload>
+            </response>
+          </payload>
+        </CANOpenSocketCANFrame>
+      </incoming-plc-message>
+      <api-response name="Report Read Response to application">
+        <DefaultPlcReadResponse className="org.apache.plc4x.java.spi.messages.DefaultPlcReadResponse">
+          <request className="org.apache.plc4x.java.spi.messages.DefaultPlcReadRequest">
+            <sdo1 className="org.apache.plc4x.java.can.field.CANOpenSDOField">
+              <nodeId>1</nodeId>
+              <index>1000</index>
+              <subIndex>22</subIndex>
+              <canOpenDataType>UNSIGNED32</canOpenDataType>
+            </sdo1>
+          </request>
+          <sdo1>
+            <code>OK</code>
+            <value className="org.apache.plc4x.java.api.value.PlcUDINT">
+              <object>java.lang.Long</object>
+              <object>1717859169</object>
+            </value>
+          </sdo1>
+        </DefaultPlcReadResponse>
+      </api-response>
+      <delay>1000</delay>
+    </steps>
+  </testcase>
+
+  <testcase>
+    <name>Segmented SDO read request</name>
+    <description>
+      Single field read request which answers with 8 bytes of data which must go over two CAN frames.
+    </description>
+    <steps>
+      <api-request name="Receive Read Request from application">
+        <TestReadRequest className="org.apache.plc4x.test.driver.model.api.TestReadRequest">
+          <fields>
+            <field className="org.apache.plc4x.test.driver.model.api.TestField">
+              <name>sdo1</name>
+              <address>SDO:2:2000/44:RECORD</address>
+            </field>
+          </fields>
+        </TestReadRequest>
+      </api-request>
+
+      <outgoing-plc-message name="Send SDO Initialize Upload Request">
+        <CANOpenSocketCANFrame className="org.apache.plc4x.java.can.canopen.socketcan.CANOpenSocketCANFrame">
+          <nodeId>2</nodeId>
+          <service>RECEIVE_SDO</service>
+          <payload className="org.apache.plc4x.java.canopen.readwrite.CANOpenSDORequest">
+            <command>INITIATE_UPLOAD</command>
+            <request className="org.apache.plc4x.java.canopen.readwrite.SDOInitiateUploadRequest">
+              <address className="org.apache.plc4x.java.canopen.readwrite.IndexAddress">
+                <index>2000</index>
+                <subindex>44</subindex>
+              </address>
+            </request>
+          </payload>
+        </CANOpenSocketCANFrame>
+      </outgoing-plc-message>
+      <incoming-plc-message name="Receive SDO Initialize Upload Response with segment information">
+        <CANOpenSocketCANFrame className="org.apache.plc4x.java.can.canopen.socketcan.CANOpenSocketCANFrame">
+          <nodeId>2</nodeId>
+          <service>TRANSMIT_SDO</service>
+          <payload className="org.apache.plc4x.java.canopen.readwrite.CANOpenSDOResponse">
+            <command>INITIATE_UPLOAD</command>
+            <response className="org.apache.plc4x.java.canopen.readwrite.SDOInitiateUploadResponse">
+              <expedited>false</expedited>
+              <indicated>true</indicated>
+              <address className="org.apache.plc4x.java.canopen.readwrite.IndexAddress">
+                <index>2000</index>
+                <subindex>44</subindex>
+              </address>
+              <payload className="org.apache.plc4x.java.canopen.readwrite.SDOInitiateSegmentedUploadResponse">
+                <bytes>8</bytes>
+              </payload>
+            </response>
+          </payload>
+        </CANOpenSocketCANFrame>
+      </incoming-plc-message>
+
+      <outgoing-plc-message name="Send first SDO Segment Request">
+        <CANOpenSocketCANFrame className="org.apache.plc4x.java.can.canopen.socketcan.CANOpenSocketCANFrame">
+          <nodeId>2</nodeId>
+          <service>RECEIVE_SDO</service>
+          <payload className="org.apache.plc4x.java.canopen.readwrite.CANOpenSDORequest">
+            <command>SEGMENT_UPLOAD</command>
+            <request className="org.apache.plc4x.java.canopen.readwrite.SDOSegmentUploadRequest">
+              <toggle>false</toggle>
+            </request>
+          </payload>
+        </CANOpenSocketCANFrame>
+      </outgoing-plc-message>
+      <incoming-plc-message name="Receive SDO Initialize Upload Response for first segment">
+        <CANOpenSocketCANFrame className="org.apache.plc4x.java.can.canopen.socketcan.CANOpenSocketCANFrame">
+          <nodeId>2</nodeId>
+          <service>TRANSMIT_SDO</service>
+          <payload className="org.apache.plc4x.java.canopen.readwrite.CANOpenSDOResponse">
+            <command>SEGMENT_UPLOAD</command>
+            <response className="org.apache.plc4x.java.canopen.readwrite.SDOSegmentUploadResponse">
+              <toggle>false</toggle>
+              <last>false</last>
+              <data>YXNkZg==</data>
+            </response>
+          </payload>
+        </CANOpenSocketCANFrame>
+      </incoming-plc-message>
+
+      <outgoing-plc-message name="Send SDO Initialize Upload Request">
+        <CANOpenSocketCANFrame className="org.apache.plc4x.java.can.canopen.socketcan.CANOpenSocketCANFrame">
+          <nodeId>2</nodeId>
+          <service>RECEIVE_SDO</service>
+          <payload className="org.apache.plc4x.java.canopen.readwrite.CANOpenSDORequest">
+            <command>SEGMENT_UPLOAD</command>
+            <request className="org.apache.plc4x.java.canopen.readwrite.SDOSegmentUploadRequest">
+              <toggle>true</toggle>
+            </request>
+          </payload>
+        </CANOpenSocketCANFrame>
+      </outgoing-plc-message>
+      <incoming-plc-message name="Receive SDO Initialize Upload Response with segment information">
+        <CANOpenSocketCANFrame className="org.apache.plc4x.java.can.canopen.socketcan.CANOpenSocketCANFrame">
+          <nodeId>2</nodeId>
+          <service>TRANSMIT_SDO</service>
+          <payload className="org.apache.plc4x.java.canopen.readwrite.CANOpenSDOResponse">
+            <command>SEGMENT_UPLOAD</command>
+            <response className="org.apache.plc4x.java.canopen.readwrite.SDOSegmentUploadResponse">
+              <toggle>true</toggle>
+              <last>false</last>
+              <data>YXNkZg==</data>
+            </response>
+          </payload>
+        </CANOpenSocketCANFrame>
+      </incoming-plc-message>
+
+      <outgoing-plc-message name="Send second SDO Segment Request">
+        <CANOpenSocketCANFrame className="org.apache.plc4x.java.can.canopen.socketcan.CANOpenSocketCANFrame">
+          <nodeId>2</nodeId>
+          <service>RECEIVE_SDO</service>
+          <payload className="org.apache.plc4x.java.canopen.readwrite.CANOpenSDORequest">
+            <command>SEGMENT_UPLOAD</command>
+            <request className="org.apache.plc4x.java.canopen.readwrite.SDOSegmentUploadRequest">
+              <toggle>false</toggle>
+            </request>
+          </payload>
+        </CANOpenSocketCANFrame>
+      </outgoing-plc-message>
+      <incoming-plc-message name="Receive SDO Initialize Upload Response for second segment">
+        <CANOpenSocketCANFrame className="org.apache.plc4x.java.can.canopen.socketcan.CANOpenSocketCANFrame">
+          <nodeId>2</nodeId>
+          <service>TRANSMIT_SDO</service>
+          <payload className="org.apache.plc4x.java.canopen.readwrite.CANOpenSDOResponse">
+            <command>SEGMENT_UPLOAD</command>
+            <response className="org.apache.plc4x.java.canopen.readwrite.SDOSegmentUploadResponse">
+              <toggle>false</toggle>
+              <last>true</last>
+              <data>YXNkZg==</data>
+            </response>
+          </payload>
+        </CANOpenSocketCANFrame>
+      </incoming-plc-message>
+
+      <api-response name="Report Read Response to application">
+        <DefaultPlcReadResponse className="org.apache.plc4x.java.spi.messages.DefaultPlcReadResponse">
+          <request className="org.apache.plc4x.java.spi.messages.DefaultPlcReadRequest">
+            <sdo1 className="org.apache.plc4x.java.can.field.CANOpenSDOField">
+              <nodeId>2</nodeId>
+              <index>2000</index>
+              <subIndex>44</subIndex>
+              <canOpenDataType>RECORD</canOpenDataType>
+            </sdo1>
+          </request>
+          <sdo1>
+            <code>OK</code>
+            <value className="org.apache.plc4x.java.api.value.PlcList">
+              <object>java.util.Collections..UnmodifiableRandomAccessList</object>
+              <object>org.apache.plc4x.java.api.value.PlcSINT</object>
+              <object>
+                <object>java.lang.Byte</object>
+                <object>97</object>
+              </object>
+              <object>org.apache.plc4x.java.api.value.PlcSINT</object>
+              <object>
+                <object>java.lang.Byte</object>
+                <object>115</object>
+              </object>
+              <object>org.apache.plc4x.java.api.value.PlcSINT</object>
+              <object>
+                <object>java.lang.Byte</object>
+                <object>100</object>
+              </object>
+              <object>org.apache.plc4x.java.api.value.PlcSINT</object>
+              <object>
+                <object>java.lang.Byte</object>
+                <object>102</object>
+              </object>
+              <object>org.apache.plc4x.java.api.value.PlcSINT</object>
+              <object>
+                <object>java.lang.Byte</object>
+                <object>97</object>
+              </object>
+              <object>org.apache.plc4x.java.api.value.PlcSINT</object>
+              <object>
+                <object>java.lang.Byte</object>
+                <object>115</object>
+              </object>
+              <object>org.apache.plc4x.java.api.value.PlcSINT</object>
+              <object>
+                <object>java.lang.Byte</object>
+                <object>100</object>
+              </object>
+              <object>org.apache.plc4x.java.api.value.PlcSINT</object>
+              <object>
+                <object>java.lang.Byte</object>
+                <object>102</object>
+              </object>
+            </value>
+          </sdo1>
+        </DefaultPlcReadResponse>
+      </api-response>
+      <delay>1000</delay>
+    </steps>
+  </testcase>
+
+</test:driver-testsuite>
\ No newline at end of file