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