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/22 11:55:28 UTC

[plc4x] 19/22: Combine socketcan and canopen frames together through dedicated IO classes.

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 231f3265eecf6900e915999d1f94f32af22d0d25
Author: Ɓukasz Dywicki <lu...@code-house.org>
AuthorDate: Sun Oct 11 01:36:32 2020 +0200

    Combine socketcan and canopen frames together through dedicated IO classes.
    
    CANOpenFrame is interface and can be still built and parsed witohut depending on specific transport.
    Additional cleanups in conversation API.
---
 .../apache/plc4x/java/can/CANOpenPlcDriver.java    |  13 +-
 .../org/apache/plc4x/java/can/api/CANFrame.java    |  11 --
 .../apache/plc4x/java/can/api/CANOpenFrame.java    |  66 ---------
 .../api/conversation/canopen/CANConversation.java  |  16 +--
 .../api/conversation/canopen/CANFrameBuilder.java  |  11 --
 .../conversation/canopen/CANOpenConversation.java  |  68 ++-------
 .../canopen/CANOpenConversationBase.java           |   1 +
 .../api/conversation/canopen/SDOConversation.java  |  23 +--
 .../canopen/SDODownloadConversation.java           |  35 +++--
 .../canopen/SDOUploadConversation.java             |  50 ++-----
 .../plc4x/java/can/canopen/CANOpenFrame.java       |  15 ++
 .../java/can/canopen/CANOpenFrameBuilder.java      |  16 +++
 .../can/canopen/CANOpenFrameBuilderFactory.java    |   7 +
 .../canopen/socketcan/CANOpenSocketCANFrame.java   | 160 +++++++++++++++++++++
 .../socketcan/CANOpenSocketCANFrameBuilder.java    |  37 +++++
 .../socketcan/io/CANOpenSocketCANFrameIO.java      | 159 ++++++++++++++++++++
 .../java/can/protocol/CANOpenProtocolLogic.java    | 143 ++++++++----------
 .../java/can/socketcan/SocketCANConversation.java  |  39 ++---
 .../java/can/socketcan/SocketCANDelegateFrame.java |  43 ------
 .../java/can/socketcan/SocketCANFrameBuilder.java  |  29 ----
 .../apache/plc4x/java/can/CANOpenDriverSDOIT.java  |  11 ++
 .../plc4x/java/can/field/CANOpenPDOFieldTest.java  |  25 ++++
 22 files changed, 572 insertions(+), 406 deletions(-)

diff --git a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/CANOpenPlcDriver.java b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/CANOpenPlcDriver.java
index c34806f..732cbfb 100644
--- a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/CANOpenPlcDriver.java
+++ b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/CANOpenPlcDriver.java
@@ -19,13 +19,12 @@
 package org.apache.plc4x.java.can;
 
 import io.netty.buffer.ByteBuf;
+import org.apache.plc4x.java.can.canopen.CANOpenFrame;
+import org.apache.plc4x.java.can.canopen.socketcan.io.CANOpenSocketCANFrameIO;
 import org.apache.plc4x.java.can.configuration.CANConfiguration;
 import org.apache.plc4x.java.can.context.CANOpenDriverContext;
-import org.apache.plc4x.java.can.field.CANFieldHandler;
 import org.apache.plc4x.java.can.field.CANOpenFieldHandler;
 import org.apache.plc4x.java.can.protocol.CANOpenProtocolLogic;
-import org.apache.plc4x.java.socketcan.readwrite.SocketCANFrame;
-import org.apache.plc4x.java.socketcan.readwrite.io.SocketCANFrameIO;
 import org.apache.plc4x.java.spi.configuration.Configuration;
 import org.apache.plc4x.java.spi.connection.GeneratedDriverBase;
 import org.apache.plc4x.java.spi.connection.ProtocolStackConfigurer;
@@ -35,7 +34,7 @@ import java.util.function.ToIntFunction;
 
 /**
  */
-public class CANOpenPlcDriver extends GeneratedDriverBase<SocketCANFrame> {
+public class CANOpenPlcDriver extends GeneratedDriverBase<CANOpenFrame> {
 
     @Override
     public String getProtocolCode() {
@@ -44,7 +43,7 @@ public class CANOpenPlcDriver extends GeneratedDriverBase<SocketCANFrame> {
 
     @Override
     public String getProtocolName() {
-        return "CANopen";
+        return "CAN Open";
     }
 
     @Override
@@ -78,8 +77,8 @@ public class CANOpenPlcDriver extends GeneratedDriverBase<SocketCANFrame> {
     }
 
     @Override
-    protected ProtocolStackConfigurer<SocketCANFrame> getStackConfigurer() {
-        return SingleProtocolStackConfigurer.builder(SocketCANFrame.class, SocketCANFrameIO.class)
+    protected ProtocolStackConfigurer<CANOpenFrame> getStackConfigurer() {
+        return SingleProtocolStackConfigurer.builder(CANOpenFrame.class, CANOpenSocketCANFrameIO.class)
             .withProtocol(CANOpenProtocolLogic.class)
             .withDriverContext(CANOpenDriverContext.class)
             .withPacketSizeEstimator(CANEstimator.class)
diff --git a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/api/CANFrame.java b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/api/CANFrame.java
deleted file mode 100644
index ff1cad6..0000000
--- a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/api/CANFrame.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package org.apache.plc4x.java.can.api;
-
-public interface CANFrame {
-
-    int getIdentifier();
-    boolean getExtended();
-    boolean getRemote();
-    boolean getError();
-    byte[] getData();
-
-}
diff --git a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/api/CANOpenFrame.java b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/api/CANOpenFrame.java
deleted file mode 100644
index 27db3c2..0000000
--- a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/api/CANOpenFrame.java
+++ /dev/null
@@ -1,66 +0,0 @@
-package org.apache.plc4x.java.can.api;
-
-import org.apache.plc4x.java.api.exceptions.PlcRuntimeException;
-import org.apache.plc4x.java.canopen.readwrite.CANOpenPayload;
-import org.apache.plc4x.java.canopen.readwrite.io.CANOpenPayloadIO;
-import org.apache.plc4x.java.canopen.readwrite.types.CANOpenService;
-import org.apache.plc4x.java.spi.generation.ParseException;
-import org.apache.plc4x.java.spi.generation.ReadBuffer;
-
-public class CANOpenFrame implements CANFrame {
-
-    private final CANFrame frame;
-
-    private final CANOpenPayload payload;
-
-    public CANOpenFrame(CANFrame frame) {
-        this.frame = frame;
-        try {
-            this.payload = CANOpenPayloadIO.staticParse(new ReadBuffer(frame.getData(), true), serviceId(frame.getIdentifier()));
-        } catch (ParseException e) {
-            throw new PlcRuntimeException("Could not parse CANopen payload", e);
-        }
-    }
-
-    public CANOpenPayload getPayload() {
-        return payload;
-    }
-
-    @Override
-    public int getIdentifier() {
-        return frame.getIdentifier();
-    }
-
-    @Override
-    public boolean getExtended() {
-        return frame.getExtended();
-    }
-
-    @Override
-    public boolean getRemote() {
-        return frame.getRemote();
-    }
-
-    @Override
-    public boolean getError() {
-        return frame.getError();
-    }
-
-    @Override
-    public byte[] getData() {
-        return frame.getData();
-    }
-
-    private CANOpenService serviceId(int cobId) {
-        // form 32 bit socketcan identifier
-        CANOpenService service = CANOpenService.valueOf((byte) (cobId >> 7));
-        if (service == null) {
-            for (CANOpenService val : CANOpenService.values()) {
-                if (val.getMin() > cobId && val.getMax() < cobId) {
-                    return val;
-                }
-            }
-        }
-        return service;
-    }
-}
diff --git a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/api/conversation/canopen/CANConversation.java b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/api/conversation/canopen/CANConversation.java
index be4675b..471e8a6 100644
--- a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/api/conversation/canopen/CANConversation.java
+++ b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/api/conversation/canopen/CANConversation.java
@@ -1,20 +1,18 @@
 package org.apache.plc4x.java.can.api.conversation.canopen;
 
-import org.apache.plc4x.java.can.api.CANFrame;
-
-import java.util.function.BiConsumer;
-
+import org.apache.plc4x.java.can.canopen.CANOpenFrame;
+import org.apache.plc4x.java.can.canopen.CANOpenFrameBuilder;
 import org.apache.plc4x.java.spi.ConversationContext.SendRequestContext;
-import org.apache.plc4x.java.spi.transaction.RequestTransactionManager.RequestTransaction;
 
-public interface CANConversation<W extends CANFrame> {
+import java.util.function.Consumer;
 
-    int getNodeId();
+public interface CANConversation<W extends CANOpenFrame> {
 
-    CANFrameBuilder<W> frameBuilder();
+    int getNodeId();
 
-    void send(RequestTransaction transaction, W frame, BiConsumer<RequestTransaction, SendRequestContext<W>> callback);
+    CANOpenFrameBuilder createBuilder();
 
+    void send(W frame, Consumer<SendRequestContext<W>> callback);
 
 }
 
diff --git a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/api/conversation/canopen/CANFrameBuilder.java b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/api/conversation/canopen/CANFrameBuilder.java
deleted file mode 100644
index 53a68c3..0000000
--- a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/api/conversation/canopen/CANFrameBuilder.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package org.apache.plc4x.java.can.api.conversation.canopen;
-
-public interface CANFrameBuilder<W> {
-
-    CANFrameBuilder<W> node(int node);
-
-    CANFrameBuilder<W> data(byte[] data);
-
-    W build();
-
-}
\ No newline at end of file
diff --git a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/api/conversation/canopen/CANOpenConversation.java b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/api/conversation/canopen/CANOpenConversation.java
index fee418d..9785fee 100644
--- a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/api/conversation/canopen/CANOpenConversation.java
+++ b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/api/conversation/canopen/CANOpenConversation.java
@@ -1,85 +1,41 @@
 package org.apache.plc4x.java.can.api.conversation.canopen;
 
-import org.apache.plc4x.java.api.exceptions.PlcRuntimeException;
-import org.apache.plc4x.java.can.api.CANFrame;
+import org.apache.plc4x.java.can.canopen.CANOpenFrame;
 import org.apache.plc4x.java.canopen.readwrite.CANOpenPayload;
-import org.apache.plc4x.java.canopen.readwrite.io.CANOpenPayloadIO;
 import org.apache.plc4x.java.canopen.readwrite.types.CANOpenService;
 import org.apache.plc4x.java.spi.ConversationContext.SendRequestContext;
-import org.apache.plc4x.java.spi.generation.ParseException;
-import org.apache.plc4x.java.spi.generation.ReadBuffer;
-import org.apache.plc4x.java.spi.generation.WriteBuffer;
-import org.apache.plc4x.java.spi.transaction.RequestTransactionManager;
-import org.apache.plc4x.java.spi.transaction.RequestTransactionManager.RequestTransaction;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.util.concurrent.CompletableFuture;
-import java.util.function.BiConsumer;
+import java.util.function.Consumer;
 
-public class CANOpenConversation<W extends CANFrame> {
+public class CANOpenConversation {
 
     private final Logger logger = LoggerFactory.getLogger(CANOpenConversation.class);
-    private final RequestTransaction transaction;
     private final int node;
-    private final CANConversation<W> delegate;
+    private final CANConversation<CANOpenFrame> delegate;
 
-    public CANOpenConversation(RequestTransaction transaction, int node, CANConversation<W> delegate) {
-        this.transaction = transaction;
+    public CANOpenConversation(int node, CANConversation<CANOpenFrame> delegate) {
         this.node = node;
         this.delegate = delegate;
     }
 
-    public SDOConversation<W> sdo() {
-        return new SDOConversation<>(this);
+    public SDOConversation sdo() {
+        return new SDOConversation(this);
     }
 
-    public void send(CANOpenService service, CANOpenPayload payload, BiConsumer<RequestTransaction, SendRequestContext<CANOpenPayload>> callback) {
-        CANFrameBuilder<W> builder = delegate.frameBuilder();
-        W frame = builder.node(service.getMin() + node).data(serialize(payload)).build();
-        logger.info("Request data under transaction {}", transaction);
-        delegate.send(transaction, frame, (tx, ctx) -> {
+    public void send(CANOpenService service, CANOpenPayload payload, Consumer<SendRequestContext<CANOpenPayload>> callback) {
+        CANOpenFrame frame = delegate.createBuilder().withNodeId(node).withService(service).withPayload(payload).build();
+        delegate.send(frame, (ctx) -> {
             SendRequestContext<CANOpenPayload> unwrap = ctx
 //                .onError((response, error) -> {
 //                    System.err.println("Unexpected frame " + response + " " + error);
 //                })
-            .unwrap(CANOpenConversation.this::deserialize);
-            callback.accept(tx, unwrap);
+            .unwrap(CANOpenFrame::getPayload);
+            callback.accept(unwrap);
         });
 
 
     }
 
-    private CANOpenPayload deserialize(CANFrame frame) {
-        try {
-            CANOpenService service = serviceId(frame.getIdentifier());
-            ReadBuffer buffer = new ReadBuffer(frame.getData(), true);
-            return CANOpenPayloadIO.staticParse(buffer, service);
-        } catch (ParseException e) {
-            throw new PlcRuntimeException("Could not deserialize CAN open payload", e);
-        }
-    }
-
-    private byte[] serialize(CANOpenPayload payload) {
-        try {
-            WriteBuffer buffer = new WriteBuffer(payload.getLengthInBytes(), true);
-            CANOpenPayloadIO.staticSerialize(buffer, payload);
-            return buffer.getData();
-        } catch (ParseException e) {
-            throw new PlcRuntimeException("Could not serialize CAN open payload", e);
-        }
-    }
-
-    private CANOpenService serviceId(int cobId) {
-        // form 32 bit socketcan identifier
-        CANOpenService service = CANOpenService.valueOf((byte) (cobId >> 7));
-        if (service == null) {
-            for (CANOpenService val : CANOpenService.values()) {
-                if (val.getMin() > cobId && val.getMax() < cobId) {
-                    return val;
-                }
-            }
-        }
-        return service;
-    }
 }
diff --git a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/api/conversation/canopen/CANOpenConversationBase.java b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/api/conversation/canopen/CANOpenConversationBase.java
index 60b87e4..0c65988 100644
--- a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/api/conversation/canopen/CANOpenConversationBase.java
+++ b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/api/conversation/canopen/CANOpenConversationBase.java
@@ -1,6 +1,7 @@
 package org.apache.plc4x.java.can.api.conversation.canopen;
 
 import org.apache.plc4x.java.api.value.PlcValue;
+import org.apache.plc4x.java.can.canopen.CANOpenFrame;
 import org.apache.plc4x.java.canopen.readwrite.io.DataItemIO;
 import org.apache.plc4x.java.canopen.readwrite.types.CANOpenDataType;
 import org.apache.plc4x.java.spi.generation.ParseException;
diff --git a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/api/conversation/canopen/SDOConversation.java b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/api/conversation/canopen/SDOConversation.java
index 1447f08..251e4da 100644
--- a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/api/conversation/canopen/SDOConversation.java
+++ b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/api/conversation/canopen/SDOConversation.java
@@ -1,7 +1,7 @@
 package org.apache.plc4x.java.can.api.conversation.canopen;
 
 import org.apache.plc4x.java.api.value.PlcValue;
-import org.apache.plc4x.java.can.api.CANFrame;
+import org.apache.plc4x.java.can.canopen.CANOpenFrame;
 import org.apache.plc4x.java.canopen.readwrite.CANOpenSDORequest;
 import org.apache.plc4x.java.canopen.readwrite.CANOpenSDOResponse;
 import org.apache.plc4x.java.canopen.readwrite.IndexAddress;
@@ -12,31 +12,32 @@ import org.apache.plc4x.java.spi.ConversationContext.SendRequestContext;
 import org.apache.plc4x.java.spi.transaction.RequestTransactionManager.RequestTransaction;
 
 import java.util.function.BiConsumer;
+import java.util.function.Consumer;
 
-public class SDOConversation<W extends CANFrame> {
+public class SDOConversation {
 
-    private final CANOpenConversation<W> delegate;
+    private final CANOpenConversation delegate;
 
-    public SDOConversation(CANOpenConversation<W> delegate) {
+    public SDOConversation(CANOpenConversation delegate) {
         this.delegate = delegate;
     }
 
-    public SDODownloadConversation<W> download(IndexAddress indexAddress, PlcValue value, CANOpenDataType type) {
-        return new SDODownloadConversation<>(this, indexAddress, value, type);
+    public SDODownloadConversation download(IndexAddress indexAddress, PlcValue value, CANOpenDataType type) {
+        return new SDODownloadConversation(this, indexAddress, value, type);
     }
 
-    public SDOUploadConversation<W> upload(IndexAddress indexAddress, CANOpenDataType type) {
-        return new SDOUploadConversation<>(this, indexAddress, type);
+    public SDOUploadConversation upload(IndexAddress indexAddress, CANOpenDataType type) {
+        return new SDOUploadConversation(this, indexAddress, type);
     }
 
-    public void send(SDORequest request, BiConsumer<RequestTransaction, SendRequestContext<CANOpenSDOResponse>> callback) {
-        delegate.send(CANOpenService.RECEIVE_SDO, new CANOpenSDORequest(request.getCommand(), request), (tx, ctx) -> {
+    public void send(SDORequest request, Consumer<SendRequestContext<CANOpenSDOResponse>> callback) {
+        delegate.send(CANOpenService.RECEIVE_SDO, new CANOpenSDORequest(request.getCommand(), request), (ctx) -> {
             SendRequestContext<CANOpenSDOResponse> context = ctx
 //            .onError((response, error) -> {
 //                System.out.println("Unexpected frame " + response + " " + error);
 //            })
             .only(CANOpenSDOResponse.class);
-            callback.accept(tx, context);
+            callback.accept(context);
         });
     }
 
diff --git a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/api/conversation/canopen/SDODownloadConversation.java b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/api/conversation/canopen/SDODownloadConversation.java
index 470e190..8e90bb6 100644
--- a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/api/conversation/canopen/SDODownloadConversation.java
+++ b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/api/conversation/canopen/SDODownloadConversation.java
@@ -4,26 +4,22 @@ import org.apache.plc4x.java.api.exceptions.PlcException;
 import org.apache.plc4x.java.api.exceptions.PlcRuntimeException;
 import org.apache.plc4x.java.api.types.PlcResponseCode;
 import org.apache.plc4x.java.api.value.PlcValue;
-import org.apache.plc4x.java.can.api.CANFrame;
-import org.apache.plc4x.java.can.api.segmentation.accumulator.ByteStorage;
 import org.apache.plc4x.java.canopen.readwrite.*;
 import org.apache.plc4x.java.canopen.readwrite.io.DataItemIO;
 import org.apache.plc4x.java.canopen.readwrite.types.CANOpenDataType;
 import org.apache.plc4x.java.canopen.readwrite.types.SDOResponseCommand;
 import org.apache.plc4x.java.spi.generation.ParseException;
 
-import java.util.concurrent.atomic.AtomicBoolean;
-import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.CompletableFuture;
 import java.util.function.BiConsumer;
 
-public class SDODownloadConversation<W extends CANFrame> {
+public class SDODownloadConversation extends CANOpenConversationBase {
 
-    private final SDOConversation<W> delegate;
+    private final SDOConversation delegate;
     private final IndexAddress indexAddress;
     private final byte[] data;
 
-    public SDODownloadConversation(SDOConversation<W> delegate, IndexAddress indexAddress, PlcValue value, CANOpenDataType type) {
+    public SDODownloadConversation(SDOConversation delegate, IndexAddress indexAddress, PlcValue value, CANOpenDataType type) {
         this.delegate = delegate;
         this.indexAddress = indexAddress;
 
@@ -34,12 +30,12 @@ public class SDODownloadConversation<W extends CANFrame> {
         }
     }
 
-    public void execute(BiConsumer<PlcResponseCode, Throwable> receiver) throws PlcException {
+    public void execute(CompletableFuture<PlcResponseCode> receiver) {
         if (data.length > 4) {
             // segmented
 
             SDOInitiateSegmentedUploadResponse size = new SDOInitiateSegmentedUploadResponse(data.length);
-            delegate.send(new SDOInitiateDownloadRequest(false, true, indexAddress, size), (tx, ctx) -> {
+            delegate.send(new SDOInitiateDownloadRequest(false, true, indexAddress, size), (ctx) -> {
                 ctx.unwrap(CANOpenSDOResponse::getResponse)
                     .check(p -> p.getCommand() == SDOResponseCommand.INITIATE_DOWNLOAD)
                     .only(SDOInitiateDownloadResponse.class)
@@ -59,11 +55,12 @@ public class SDODownloadConversation<W extends CANFrame> {
             new SDOInitiateExpeditedUploadResponse(data)
         );
 
-        delegate.send(rq, (tx, ctx) ->
+        delegate.send(rq, (ctx) ->
             ctx.onError((response, error) -> {
                 System.out.println("Unexpected frame " + response + " " + error);
             })
             .unwrap(CANOpenSDOResponse::getResponse)
+            .only(SDOInitiateDownloadResponse.class)
             .check(r -> r.getCommand() == SDOResponseCommand.INITIATE_DOWNLOAD)
             .handle(r -> {
                 System.out.println(r);
@@ -71,23 +68,25 @@ public class SDODownloadConversation<W extends CANFrame> {
         );
     }
 
-    private void put(byte[] data, BiConsumer<PlcResponseCode, Throwable> receiver, boolean toggle, int offset) {
+    private void put(byte[] data, CompletableFuture<PlcResponseCode> receiver, boolean toggle, int offset) {
         int remaining = data.length - offset;
         byte[] segment = new byte[Math.min(remaining, 7)];
         System.arraycopy(data, offset, segment, 0, segment.length);
 
-        delegate.send(new SDOSegmentDownloadRequest(toggle, remaining <= 7, segment), (tx, ctx) -> {
+        delegate.send(new SDOSegmentDownloadRequest(toggle, remaining <= 7, segment), (ctx) -> {
             ctx.unwrap(CANOpenSDOResponse::getResponse)
                 .only(SDOSegmentDownloadResponse.class)
                 .onError((response, error) -> {
-                    System.out.println("Unexpected frame " + response + " " + error);
-                    receiver.accept(null, error);
+                    if (error != null) {
+                        receiver.completeExceptionally(error);
+                    } else {
+                        receiver.completeExceptionally(new PlcException("Transaction terminated"));
+                    }
                 })
-                .check(r -> r.getToggle() == toggle)
+                .check(response -> response.getToggle() == toggle)
                 .handle(reply -> {
                     if (offset + segment.length == data.length) {
-                        // validate offset
-                        receiver.accept(PlcResponseCode.OK, null);
+                        receiver.complete(PlcResponseCode.OK);
                     } else {
                         put(data, receiver, !toggle, offset + segment.length);
                     }
diff --git a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/api/conversation/canopen/SDOUploadConversation.java b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/api/conversation/canopen/SDOUploadConversation.java
index 0bb0998..0beb17c 100644
--- a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/api/conversation/canopen/SDOUploadConversation.java
+++ b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/api/conversation/canopen/SDOUploadConversation.java
@@ -2,37 +2,34 @@ package org.apache.plc4x.java.can.api.conversation.canopen;
 
 import org.apache.plc4x.java.api.exceptions.PlcException;
 import org.apache.plc4x.java.api.value.PlcValue;
-import org.apache.plc4x.java.can.api.CANFrame;
 import org.apache.plc4x.java.can.api.segmentation.accumulator.ByteStorage;
 import org.apache.plc4x.java.canopen.readwrite.*;
 import org.apache.plc4x.java.canopen.readwrite.types.CANOpenDataType;
 import org.apache.plc4x.java.spi.generation.ParseException;
-import org.apache.plc4x.java.spi.transaction.RequestTransactionManager;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.util.concurrent.CompletableFuture;
 import java.util.function.BiConsumer;
 
-public class SDOUploadConversation<W extends CANFrame> extends CANOpenConversationBase {
+public class SDOUploadConversation extends CANOpenConversationBase {
 
     private final Logger logger = LoggerFactory.getLogger(SDOUploadConversation.class);
-    private final SDOConversation<W> delegate;
+    private final SDOConversation delegate;
     private final IndexAddress address;
     private final CANOpenDataType type;
 
-    public SDOUploadConversation(SDOConversation<W> delegate, IndexAddress address, CANOpenDataType type) {
+    public SDOUploadConversation(SDOConversation delegate, IndexAddress address, CANOpenDataType type) {
         this.delegate = delegate;
         this.address = address;
         this.type = type;
     }
 
-    public void execute(CompletableFuture<PlcValue> receiver) throws PlcException {
+    public void execute(CompletableFuture<PlcValue> receiver) {
         SDOInitiateUploadRequest rq = new SDOInitiateUploadRequest(address);
 
-        delegate.send(rq, (tx, ctx) ->
+        delegate.send(rq, (ctx) ->
             ctx.onError((response, error) -> {
-                System.err.println("Unexpected frame " + response + " " + error);
                 if (error != null) {
                     receiver.completeExceptionally(error);
                     return;
@@ -43,34 +40,16 @@ public class SDOUploadConversation<W extends CANFrame> extends CANOpenConversati
                     receiver.completeExceptionally(new PlcException("Could not read value. Remote party reported code " + sdoAbort.getCode()));
                 }
             })
-            .check(reply -> {
-                logger.warn("Received answer {}", reply);
-                return true;
-            })
-            .unwrap(CANOpenSDOResponse::getResponse).check(reply -> {
-                logger.warn("Received answer {}", reply);
-                return true;
-            })
-            .check(reply -> {
-                logger.warn("Received answer {}", reply);
-                return true;
-            })
+            .unwrap(CANOpenSDOResponse::getResponse)
             .only(SDOInitiateUploadResponse.class)
-            .check(resp -> {
-                logger.warn("Checking if reply address {}/{} matches {}/{}: {}",
-                    Integer.toHexString(resp.getAddress().getIndex()), Integer.toHexString(resp.getAddress().getSubindex()),
-                    Integer.toHexString(address.getIndex()), Integer.toHexString(address.getSubindex()),
-                    resp.getAddress().equals(address)
-                );
-                return resp.getAddress().equals(address);
-            })
+            .check(response -> response.getAddress().equals(address))
             .handle(response -> {
-                handle(tx, receiver, response);
+                handle(receiver, response);
             })
         );
     }
 
-    private void handle(RequestTransactionManager.RequestTransaction tx, CompletableFuture<PlcValue> receiver, SDOInitiateUploadResponse answer) {
+    private void handle(CompletableFuture<PlcValue> receiver, SDOInitiateUploadResponse answer) {
         BiConsumer<Integer, byte[]> valueCallback = (length, bytes) -> {
             try {
                 final PlcValue decodedValue = decodeFrom(bytes, type, length);
@@ -84,7 +63,7 @@ public class SDOUploadConversation<W extends CANFrame> extends CANOpenConversati
             SDOInitiateExpeditedUploadResponse payload = (SDOInitiateExpeditedUploadResponse) answer.getPayload();
             valueCallback.accept(payload.getData().length, payload.getData());
         } else if (answer.getPayload() instanceof SDOInitiateSegmentedUploadResponse) {
-            logger.info("Beginning of segmented operation for address {}/{}", Integer.toHexString(address.getIndex()), Integer.toHexString(address.getSubindex()));
+            logger.debug("Beginning of segmented operation for address {}/{}", Integer.toHexString(address.getIndex()), Integer.toHexString(address.getSubindex()));
             ByteStorage.SDOUploadStorage storage = new ByteStorage.SDOUploadStorage();
             storage.append(answer);
 
@@ -97,10 +76,9 @@ public class SDOUploadConversation<W extends CANFrame> extends CANOpenConversati
 
     private void fetch(ByteStorage.SDOUploadStorage storage, BiConsumer<Integer, byte[]> valueCallback, CompletableFuture<PlcValue> receiver, boolean toggle, int size) {
         logger.info("Request next data block for address {}/{}", Integer.toHexString(address.getIndex()), Integer.toHexString(address.getSubindex()));
-        delegate.send(new SDOSegmentUploadRequest(toggle), (tx, ctx) -> {
+        delegate.send(new SDOSegmentUploadRequest(toggle), (ctx) -> {
             ctx.unwrap(CANOpenSDOResponse::getResponse)
                 .onError((response, error) -> {
-                    System.out.println("Unexpected frame " + response + " " + error);
                     if (error != null) {
                         receiver.completeExceptionally(error);
                         return;
@@ -115,12 +93,6 @@ public class SDOUploadConversation<W extends CANFrame> extends CANOpenConversati
                 .only(SDOSegmentUploadResponse.class)
                 .check(r -> r.getToggle() == toggle)
                 .handle(response -> {
-//                    if (!reply.getToggle() == toggle) { // toggle flag is wrong, abort transaction
-//                        logger.info("Received invalid answer from party for {}", address);
-//                        delegate.send(new SDOAbortRequest(new SDOAbort(address, 0x100)), (tx2, ctx2) -> {});
-//                        return;
-//                    }
-
                     storage.append(response);
 
                     if (response.getLast()) {
diff --git a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/canopen/CANOpenFrame.java b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/canopen/CANOpenFrame.java
new file mode 100644
index 0000000..2a32bda
--- /dev/null
+++ b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/canopen/CANOpenFrame.java
@@ -0,0 +1,15 @@
+package org.apache.plc4x.java.can.canopen;
+
+import org.apache.plc4x.java.canopen.readwrite.CANOpenPayload;
+import org.apache.plc4x.java.canopen.readwrite.types.CANOpenService;
+import org.apache.plc4x.java.spi.generation.Message;
+
+public interface CANOpenFrame extends Message {
+
+    int getNodeId();
+
+    CANOpenService getService();
+
+    CANOpenPayload getPayload();
+
+}
diff --git a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/canopen/CANOpenFrameBuilder.java b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/canopen/CANOpenFrameBuilder.java
new file mode 100644
index 0000000..17235b6
--- /dev/null
+++ b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/canopen/CANOpenFrameBuilder.java
@@ -0,0 +1,16 @@
+package org.apache.plc4x.java.can.canopen;
+
+import org.apache.plc4x.java.canopen.readwrite.CANOpenPayload;
+import org.apache.plc4x.java.canopen.readwrite.types.CANOpenService;
+
+public interface CANOpenFrameBuilder {
+
+    CANOpenFrameBuilder withNodeId(int node);
+
+    CANOpenFrameBuilder withService(CANOpenService service);
+
+    CANOpenFrameBuilder withPayload(CANOpenPayload payload);
+
+    CANOpenFrame build();
+
+}
diff --git a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/canopen/CANOpenFrameBuilderFactory.java b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/canopen/CANOpenFrameBuilderFactory.java
new file mode 100644
index 0000000..9704f6c
--- /dev/null
+++ b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/canopen/CANOpenFrameBuilderFactory.java
@@ -0,0 +1,7 @@
+package org.apache.plc4x.java.can.canopen;
+
+public interface CANOpenFrameBuilderFactory {
+
+    CANOpenFrameBuilder createBuilder();
+
+}
diff --git a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/canopen/socketcan/CANOpenSocketCANFrame.java b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/canopen/socketcan/CANOpenSocketCANFrame.java
new file mode 100644
index 0000000..d51f042
--- /dev/null
+++ b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/canopen/socketcan/CANOpenSocketCANFrame.java
@@ -0,0 +1,160 @@
+/*
+  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.can.canopen.socketcan;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+import org.apache.plc4x.java.can.canopen.CANOpenFrame;
+import org.apache.plc4x.java.canopen.readwrite.CANOpenPayload;
+import org.apache.plc4x.java.canopen.readwrite.types.CANOpenService;
+import org.apache.plc4x.java.spi.generation.MessageIO;
+import org.apache.plc4x.java.can.canopen.socketcan.io.CANOpenSocketCANFrameIO;
+
+import java.util.Objects;
+
+@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS, property = "className")
+public class CANOpenSocketCANFrame implements CANOpenFrame {
+
+    // Properties.
+    private final int nodeId;
+    private final CANOpenService service;
+    private final CANOpenPayload payload;
+
+    @JsonCreator(mode = JsonCreator.Mode.PROPERTIES)
+    public CANOpenSocketCANFrame(@JsonProperty("nodeId") int nodeId, @JsonProperty("service") CANOpenService service, @JsonProperty("payload") CANOpenPayload payload) {
+        this.nodeId = nodeId;
+        this.service = service;
+        this.payload = payload;
+    }
+
+    public CANOpenSocketCANFrame(int nodeId, CANOpenPayload payload) {
+        this(nodeId, payload.getFunction(), payload);
+    }
+
+    @Override
+    public int getNodeId() {
+        return nodeId;
+    }
+
+    @Override
+    public CANOpenService getService() {
+        return service;
+    }
+
+    @Override
+    public CANOpenPayload getPayload() {
+        return payload;
+    }
+
+    @Override
+    @JsonIgnore
+    public int getLengthInBytes() {
+        return getLengthInBits() / 8;
+    }
+
+    @Override
+    @JsonIgnore
+    public int getLengthInBits() {
+        int lengthInBits = 0;
+
+        // Simple field (node + service)
+        lengthInBits += 32;
+
+        // A virtual field doesn't have any in- or output.
+
+        // A virtual field doesn't have any in- or output.
+
+        // A virtual field doesn't have any in- or output.
+
+        // A virtual field doesn't have any in- or output.
+
+        // Implicit Field (size)
+        lengthInBits += 8;
+
+        // Reserved Field (reserved)
+        lengthInBits += 8;
+
+        // Reserved Field (reserved)
+        lengthInBits += 8;
+
+        // Reserved Field (reserved)
+        lengthInBits += 8;
+
+        // Array field
+        if (payload != null) {
+            lengthInBits += 8 * payload.getLengthInBytes();
+        }
+
+        // Padding Field (padding)
+        int _timesPadding = (8) - payload.getLengthInBytes();
+        while (_timesPadding-- > 0) {
+            lengthInBits += 8;
+        }
+
+        return lengthInBits;
+    }
+
+    @Override
+    @JsonIgnore
+    public MessageIO<CANOpenFrame, CANOpenFrame> getMessageIO() {
+        return new CANOpenSocketCANFrameIO();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!(o instanceof CANOpenSocketCANFrame)) {
+            return false;
+        }
+        CANOpenSocketCANFrame that = (CANOpenSocketCANFrame) o;
+        return (getNodeId() == that.getNodeId()) &&
+            (getService() == that.getService()) &&
+            (getPayload() == that.getPayload());
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(
+            getNodeId(),
+            getService(),
+            getPayload()
+        );
+    }
+
+    @Override
+    public String toString() {
+        return toString(ToStringStyle.SHORT_PREFIX_STYLE);
+    }
+
+    public String toString(ToStringStyle style) {
+        return new ToStringBuilder(this, style)
+            .append("nodeId", getNodeId())
+            .append("service", getService())
+            .append("payload", getPayload())
+            .toString();
+    }
+
+}
diff --git a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/canopen/socketcan/CANOpenSocketCANFrameBuilder.java b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/canopen/socketcan/CANOpenSocketCANFrameBuilder.java
new file mode 100644
index 0000000..87f7b2f
--- /dev/null
+++ b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/canopen/socketcan/CANOpenSocketCANFrameBuilder.java
@@ -0,0 +1,37 @@
+package org.apache.plc4x.java.can.canopen.socketcan;
+
+import org.apache.plc4x.java.can.canopen.CANOpenFrame;
+import org.apache.plc4x.java.can.canopen.CANOpenFrameBuilder;
+import org.apache.plc4x.java.canopen.readwrite.CANOpenPayload;
+import org.apache.plc4x.java.canopen.readwrite.types.CANOpenService;
+
+public class CANOpenSocketCANFrameBuilder implements CANOpenFrameBuilder {
+
+    private int node;
+    private CANOpenPayload payload;
+    private CANOpenService service;
+
+    @Override
+    public CANOpenFrameBuilder withNodeId(int node) {
+        this.node = node;
+        return this;
+    }
+
+    @Override
+    public CANOpenFrameBuilder withService(CANOpenService service) {
+        this.service = service;
+        return this;
+    }
+
+    @Override
+    public CANOpenFrameBuilder withPayload(CANOpenPayload payload) {
+        this.payload = payload;
+        return this;
+    }
+
+    @Override
+    public CANOpenFrame build() {
+        return new CANOpenSocketCANFrame(node, service, payload);
+    }
+
+}
diff --git a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/canopen/socketcan/io/CANOpenSocketCANFrameIO.java b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/canopen/socketcan/io/CANOpenSocketCANFrameIO.java
new file mode 100644
index 0000000..1b6dbf2
--- /dev/null
+++ b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/canopen/socketcan/io/CANOpenSocketCANFrameIO.java
@@ -0,0 +1,159 @@
+/*
+  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.can.canopen.socketcan.io;
+
+import org.apache.plc4x.java.can.canopen.CANOpenFrame;
+import org.apache.plc4x.java.can.canopen.socketcan.CANOpenSocketCANFrame;
+import org.apache.plc4x.java.canopen.readwrite.CANOpenPayload;
+import org.apache.plc4x.java.canopen.readwrite.io.CANOpenPayloadIO;
+import org.apache.plc4x.java.canopen.readwrite.types.CANOpenService;
+import org.apache.plc4x.java.spi.generation.MessageIO;
+import org.apache.plc4x.java.spi.generation.ParseException;
+import org.apache.plc4x.java.spi.generation.ReadBuffer;
+import org.apache.plc4x.java.spi.generation.WriteBuffer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class CANOpenSocketCANFrameIO implements MessageIO<CANOpenFrame, CANOpenFrame> {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(CANOpenSocketCANFrameIO.class);
+
+    @Override
+    public CANOpenSocketCANFrame parse(ReadBuffer io, Object... args) throws ParseException {
+        return CANOpenSocketCANFrameIO.staticParse(io);
+    }
+
+    @Override
+    public void serialize(WriteBuffer io, CANOpenFrame value, Object... args) throws ParseException {
+        CANOpenSocketCANFrameIO.staticSerialize(io, (CANOpenSocketCANFrame) value);
+    }
+
+    public static CANOpenSocketCANFrame staticParse(ReadBuffer io) throws ParseException {
+        int startPos = io.getPos();
+        int curPos;
+
+        // Simple Field (rawId)
+        int rawId = io.readInt(32);
+
+        // Virtual field (Just declare a local variable so we can access it in the parser)
+        int identifier = (int) (org.apache.plc4x.java.can.helper.HeaderParser.readIdentifier(rawId));
+
+        CANOpenService service = serviceId(identifier);
+        int nodeId = Math.abs(service.getMin() - identifier);
+
+        // Implicit Field (size) (Used for parsing, but it's value is not stored as it's implicitly given by the objects content)
+        short size = io.readUnsignedShort(8);
+
+        // Reserved Field (Compartmentalized so the "reserved" variable can't leak)
+        {
+            short reserved = io.readUnsignedShort(8);
+            if(reserved != (short) 0x0) {
+                LOGGER.info("Expected constant value " + 0x0 + " but got " + reserved + " for reserved field.");
+            }
+        }
+
+        // Reserved Field (Compartmentalized so the "reserved" variable can't leak)
+        {
+            short reserved = io.readUnsignedShort(8);
+            if(reserved != (short) 0x0) {
+                LOGGER.info("Expected constant value " + 0x0 + " but got " + reserved + " for reserved field.");
+            }
+        }
+
+        // Reserved Field (Compartmentalized so the "reserved" variable can't leak)
+        {
+            short reserved = io.readUnsignedShort(8);
+            if(reserved != (short) 0x0) {
+                LOGGER.info("Expected constant value " + 0x0 + " but got " + reserved + " for reserved field.");
+            }
+        }
+
+        // Array field (data)
+        // Count array
+        if(size > Integer.MAX_VALUE) {
+            throw new ParseException("Array count of " + (size) + " exceeds the maximum allowed count of " + Integer.MAX_VALUE);
+        }
+
+        final CANOpenPayload payload = CANOpenPayloadIO.staticParse(io, service);
+
+        // Padding Field (padding)
+        {
+            int _timesPadding = (int) ((8) - payload.getLengthInBytes());
+            while ((io.hasMore(8)) && (_timesPadding-- > 0)) {
+                // Just read the padding data and ignore it
+                io.readUnsignedShort(8);
+            }
+        }
+
+        // Create the instance
+        return new CANOpenSocketCANFrame(nodeId, service, payload);
+    }
+
+    public static void staticSerialize(WriteBuffer io, CANOpenSocketCANFrame _value) throws ParseException {
+        int startPos = io.getPos();
+
+        // Simple Field (service)
+        int nodeId = _value.getNodeId();
+        int service = _value.getService().getMin();
+        io.writeInt(32, (service + nodeId));
+
+        // Implicit Field (size) (Used for parsing, but it's value is not stored as it's implicitly given by the objects content)
+        final CANOpenPayload payload = _value.getPayload();
+        short size = (short) (payload == null ? 0 : payload.getLengthInBytes());
+        io.writeUnsignedShort(8, ((Number) (size)).shortValue());
+
+        // Reserved Field (reserved)
+        io.writeUnsignedShort(8, ((Number) (short) 0x0).shortValue());
+
+        // Reserved Field (reserved)
+        io.writeUnsignedShort(8, ((Number) (short) 0x0).shortValue());
+
+        // Reserved Field (reserved)
+        io.writeUnsignedShort(8, ((Number) (short) 0x0).shortValue());
+
+        // Array Field (data)
+        if(_value.getPayload() != null) {
+            payload.getMessageIO().serialize(io, payload);
+        }
+
+        // Padding Field (padding)
+        {
+            int _timesPadding = (int) ((8) - size);
+            while (_timesPadding-- > 0) {
+                short _paddingValue = (short) (0);
+                io.writeUnsignedShort(8, ((Number) (_paddingValue)).shortValue());
+            }
+        }
+    }
+
+    public static CANOpenService serviceId(int cobId) {
+        // form 32 bit socketcan identifier
+        CANOpenService service = CANOpenService.valueOf((byte) (cobId >> 7));
+        if (service == null) {
+            for (CANOpenService val : CANOpenService.values()) {
+                if (val.getMin() > cobId && val.getMax() < cobId) {
+                    return val;
+                }
+            }
+        }
+        return service;
+    }
+
+}
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 e384d0a..96488ec 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
@@ -19,6 +19,7 @@ under the License.
 package org.apache.plc4x.java.can.protocol;
 
 import org.apache.commons.codec.binary.Hex;
+import org.apache.plc4x.java.api.exceptions.PlcException;
 import org.apache.plc4x.java.api.exceptions.PlcRuntimeException;
 import org.apache.plc4x.java.api.messages.*;
 import org.apache.plc4x.java.api.model.PlcConsumerRegistration;
@@ -29,22 +30,21 @@ import org.apache.plc4x.java.api.types.PlcSubscriptionType;
 import org.apache.plc4x.java.api.value.PlcList;
 import org.apache.plc4x.java.api.value.PlcNull;
 import org.apache.plc4x.java.api.value.PlcValue;
-import org.apache.plc4x.java.can.api.CANFrame;
+import org.apache.plc4x.java.can.canopen.CANOpenFrame;
 import org.apache.plc4x.java.can.api.conversation.canopen.CANConversation;
 import org.apache.plc4x.java.can.api.conversation.canopen.CANOpenConversation;
 import org.apache.plc4x.java.can.api.conversation.canopen.SDODownloadConversation;
 import org.apache.plc4x.java.can.api.conversation.canopen.SDOUploadConversation;
+import org.apache.plc4x.java.can.canopen.CANOpenFrameBuilder;
+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.CANOpenPDOField;
 import org.apache.plc4x.java.can.field.CANOpenSDOField;
 import org.apache.plc4x.java.can.socketcan.SocketCANConversation;
-import org.apache.plc4x.java.canopen.readwrite.CANOpenHeartbeatPayload;
-import org.apache.plc4x.java.canopen.readwrite.CANOpenPDOPayload;
-import org.apache.plc4x.java.canopen.readwrite.CANOpenPayload;
-import org.apache.plc4x.java.canopen.readwrite.IndexAddress;
-import org.apache.plc4x.java.canopen.readwrite.io.CANOpenHeartbeatPayloadIO;
+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;
@@ -66,8 +66,6 @@ import org.apache.plc4x.java.spi.transaction.RequestTransactionManager;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
 import java.time.Duration;
 import java.time.Instant;
 import java.util.Collection;
@@ -82,16 +80,18 @@ import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Consumer;
 
-public class CANOpenProtocolLogic extends Plc4xProtocolBase<SocketCANFrame> implements HasConfiguration<CANConfiguration>, PlcSubscriber {
+public class CANOpenProtocolLogic extends Plc4xProtocolBase<CANOpenFrame> implements HasConfiguration<CANConfiguration>, PlcSubscriber {
 
     private static final Duration REQUEST_TIMEOUT = Duration.ofSeconds(10L);
     private Logger logger = LoggerFactory.getLogger(CANOpenProtocolLogic.class);
 
+    private CANOpenFrameBuilderFactory factory = CANOpenSocketCANFrameBuilder::new;
+
     private CANConfiguration configuration;
     private RequestTransactionManager tm;
     private Timer heartbeat;
     private CANOpenDriverContext canContext;
-    private CANConversation<CANFrame> conversation;
+    private CANConversation<CANOpenFrame> conversation;
 
     private Map<DefaultPlcConsumerRegistration, Consumer<PlcSubscriptionEvent>> consumers = new ConcurrentHashMap<>();
 
@@ -114,7 +114,7 @@ public class CANOpenProtocolLogic extends Plc4xProtocolBase<SocketCANFrame> impl
     }
 
     @Override
-    public void onConnect(ConversationContext<SocketCANFrame> context) {
+    public void onConnect(ConversationContext<CANOpenFrame> context) {
         try {
             if (configuration.isHeartbeat()) {
                 context.sendToWire(createFrame(new CANOpenHeartbeatPayload(NMTState.BOOTED_UP)));
@@ -138,15 +138,13 @@ public class CANOpenProtocolLogic extends Plc4xProtocolBase<SocketCANFrame> impl
     }
 
     @Override
-    public void setContext(ConversationContext<SocketCANFrame> context) {
+    public void setContext(ConversationContext<CANOpenFrame> context) {
         super.setContext(context);
-        this.conversation = new SocketCANConversation(configuration.getNodeId(), context);
+        this.conversation = new SocketCANConversation(configuration.getNodeId(), context, factory);
     }
 
-    private SocketCANFrame createFrame(CANOpenHeartbeatPayload state) throws ParseException {
-        WriteBuffer buffer = new WriteBuffer(state.getLengthInBytes(), true);
-        CANOpenHeartbeatPayloadIO.staticSerialize(buffer, state);
-        return new SocketCANFrame(cobId(configuration.getNodeId(), CANOpenService.HEARTBEAT), buffer.getData());
+    private CANOpenFrame createFrame(CANOpenHeartbeatPayload state) throws ParseException {
+        return factory.createBuilder().withNodeId(configuration.getNodeId()).withService(CANOpenService.HEARTBEAT).withPayload(state).build();
     }
 
     public CompletableFuture<PlcWriteResponse> write(PlcWriteRequest writeRequest) {
@@ -177,20 +175,24 @@ public class CANOpenProtocolLogic extends Plc4xProtocolBase<SocketCANFrame> impl
 
     private void writeInternally(InternalPlcWriteRequest writeRequest, CANOpenSDOField field, CompletableFuture<PlcWriteResponse> response) {
         final RequestTransactionManager.RequestTransaction transaction = tm.startRequest();
-        CANOpenConversation<CANFrame> canopen = new CANOpenConversation<>(transaction, field.getNodeId(), conversation);
 
-        PlcValue writeValue = writeRequest.getPlcValues().get(0);
+        String fieldName = writeRequest.getFieldNames().iterator().next();
 
-        SDODownloadConversation<CANFrame> download = canopen.sdo().download(new IndexAddress(field.getIndex(), field.getSubIndex()), writeValue, field.getCanOpenDataType());
-        try {
-            download.execute((value, error) -> {
-                String fieldName = writeRequest.getFieldNames().iterator().next();
-                response.complete(new DefaultPlcWriteResponse(writeRequest, Collections.singletonMap(fieldName, PlcResponseCode.OK)));
+        CompletableFuture<PlcResponseCode> callback = new CompletableFuture<>();
+        callback.whenComplete((code, error) -> {
+            if (error != null) {
+                response.completeExceptionally(error);
                 transaction.endRequest();
-            });
-        } catch (Exception e) {
-            response.completeExceptionally(e);
-        }
+                return;
+            }
+            response.complete(new DefaultPlcWriteResponse(writeRequest, Collections.singletonMap(fieldName, code)));
+            transaction.endRequest();
+        });
+
+        PlcValue writeValue = writeRequest.getPlcValues().get(0);
+        CANOpenConversation canopen = new CANOpenConversation(field.getNodeId(), conversation);
+        SDODownloadConversation download = canopen.sdo().download(new IndexAddress(field.getIndex(), field.getSubIndex()), writeValue, field.getCanOpenDataType());
+        transaction.submit(() -> download.execute(callback));
     }
 
     private void writeInternally(InternalPlcWriteRequest writeRequest, CANOpenPDOField field, CompletableFuture<PlcWriteResponse> response) {
@@ -200,8 +202,13 @@ public class CANOpenProtocolLogic extends Plc4xProtocolBase<SocketCANFrame> impl
             String fieldName = writeRequest.getFieldNames().iterator().next();
             WriteBuffer buffer = DataItemIO.staticSerialize(writeValue, field.getCanOpenDataType(), writeValue.getLength(), true);
             if (buffer != null) {
-                int cob = field.getService().getMin() + field.getNodeId();
-                context.sendToWire(new SocketCANFrame(cob, buffer.getData()));
+                final CANOpenPDOPayload payload = new CANOpenPDOPayload(new CANOpenPDO(buffer.getData()));
+                context.sendToWire(factory.createBuilder()
+                    .withNodeId(field.getNodeId())
+                    .withService(field.getService())
+                    .withPayload(payload)
+                    .build()
+                );
                 response.complete(new DefaultPlcWriteResponse(writeRequest, Collections.singletonMap(fieldName, PlcResponseCode.OK)));
             } else {
                 response.complete(new DefaultPlcWriteResponse(writeRequest, Collections.singletonMap(fieldName, PlcResponseCode.INVALID_DATA)));
@@ -237,8 +244,6 @@ public class CANOpenProtocolLogic extends Plc4xProtocolBase<SocketCANFrame> impl
     public CompletableFuture<PlcSubscriptionResponse> subscribe(PlcSubscriptionRequest request) {
         InternalPlcSubscriptionRequest rq = (InternalPlcSubscriptionRequest) request;
 
-        List<SubscriptionPlcField> fields = rq.getSubscriptionFields();
-
         Map<String, ResponseItem<PlcSubscriptionHandle>> answers = new LinkedHashMap<>();
         DefaultPlcSubscriptionResponse response = new DefaultPlcSubscriptionResponse(rq, answers);
 
@@ -259,42 +264,35 @@ public class CANOpenProtocolLogic extends Plc4xProtocolBase<SocketCANFrame> impl
     }
 
     private void readInternally(InternalPlcReadRequest readRequest, CANOpenSDOField field, CompletableFuture<PlcReadResponse> response) {
-        try {
-            final RequestTransactionManager.RequestTransaction transaction = tm.startRequest();
-            CANOpenConversation<CANFrame> canopen = new CANOpenConversation<>(transaction, field.getNodeId(), conversation);
-            System.out.println("----> Submit read " + field.getIndex() + "/" + field.getSubIndex() + " from " + field.getNodeId() + " " + transaction);
-            SDOUploadConversation<CANFrame> upload = canopen.sdo().upload(new IndexAddress(field.getIndex(), field.getSubIndex()), field.getCanOpenDataType());
-            CompletableFuture<PlcValue> callback = new CompletableFuture<>();
-            callback.whenComplete((value, error) -> {
-                System.out.println("<---- Received reply " + field.getIndex() + "/" + field.getSubIndex() + " from " + field.getNodeId() + " " + value + "/" + error + " " + transaction);
-                if (error != null) {
-                    response.completeExceptionally(error);
-                    transaction.endRequest();
-                    return;
-                }
+        String fieldName = readRequest.getFieldNames().iterator().next();
 
-                String fieldName = readRequest.getFieldNames().iterator().next();
-                Map<String, ResponseItem<PlcValue>> fields = new HashMap<>();
-                fields.put(fieldName, new ResponseItem<>(PlcResponseCode.OK, value));
-                response.complete(new DefaultPlcReadResponse(readRequest, fields));
+        final RequestTransactionManager.RequestTransaction transaction = tm.startRequest();
+        CompletableFuture<PlcValue> callback = new CompletableFuture<>();
+        callback.whenComplete((value, error) -> {
+            if (error != null) {
+                response.completeExceptionally(error);
                 transaction.endRequest();
-            });
-            upload.execute(callback);
-        } catch (Exception e) {
-            response.completeExceptionally(e);
-        }
+                return;
+            }
+
+            Map<String, ResponseItem<PlcValue>> fields = new HashMap<>();
+            fields.put(fieldName, new ResponseItem<>(PlcResponseCode.OK, value));
+            response.complete(new DefaultPlcReadResponse(readRequest, fields));
+            transaction.endRequest();
+        });
+
+        CANOpenConversation canopen = new CANOpenConversation(field.getNodeId(), conversation);
+        SDOUploadConversation upload = canopen.sdo().upload(new IndexAddress(field.getIndex(), field.getSubIndex()), field.getCanOpenDataType());
+        transaction.submit(() -> upload.execute(callback));
     }
 
     @Override
-    protected void decode(ConversationContext<SocketCANFrame> context, SocketCANFrame msg) throws Exception {
-        CANOpenService service = serviceId(msg.getIdentifier());
-        CANOpenPayload payload = CANOpenPayloadIO.staticParse(new ReadBuffer(msg.getData()), service);
-
-        CANOpenDriverContext.CALLBACK.receive(msg);
+    protected void decode(ConversationContext<CANOpenFrame> context, CANOpenFrame msg) throws Exception {
+        int nodeId = msg.getNodeId();
+        CANOpenService service = msg.getService();
+        CANOpenPayload payload = msg.getPayload();
 
         if (service != null) {
-            int nodeId = Math.abs(service.getMin() - msg.getIdentifier());
-
             if (service.getPdo() && payload instanceof CANOpenPDOPayload) {
                 publishEvent(service, nodeId, (CANOpenPDOPayload) payload);
             } else {
@@ -310,8 +308,6 @@ public class CANOpenProtocolLogic extends Plc4xProtocolBase<SocketCANFrame> impl
                 }
                 logger.info("Decoded CANOpen {} from {}, message {}, {}", service, nodeId, payload, hex);
             }
-        } else {
-            logger.info("CAN message {}, {}", msg.getIdentifier(), msg);
         }
 
 //        int identifier = msg.getIdentifier();
@@ -385,33 +381,16 @@ public class CANOpenProtocolLogic extends Plc4xProtocolBase<SocketCANFrame> impl
     }
 
     @Override
-    public void close(ConversationContext<SocketCANFrame> context) {
+    public void close(ConversationContext<CANOpenFrame> context) {
 
     }
 
     @Override
-    public void onDisconnect(ConversationContext<SocketCANFrame> context) {
+    public void onDisconnect(ConversationContext<CANOpenFrame> context) {
         if (this.heartbeat != null) {
             this.heartbeat.cancel();
             this.heartbeat = null;
         }
     }
 
-    private int cobId(int nodeId, CANOpenService service) {
-        // form 32 bit socketcan identifier
-        return service.getMin() + nodeId;
-    }
-
-    private CANOpenService serviceId(int cobId) {
-        // form 32 bit socketcan identifier
-        CANOpenService service = CANOpenService.valueOf((byte) (cobId >> 7));
-        if (service == null) {
-            for (CANOpenService val : CANOpenService.values()) {
-                if (val.getMin() > cobId && val.getMax() < cobId) {
-                    return val;
-                }
-            }
-        }
-        return service;
-    }
 }
diff --git a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/socketcan/SocketCANConversation.java b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/socketcan/SocketCANConversation.java
index c35843d..721b0f1 100644
--- a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/socketcan/SocketCANConversation.java
+++ b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/socketcan/SocketCANConversation.java
@@ -1,25 +1,25 @@
 package org.apache.plc4x.java.can.socketcan;
 
-import org.apache.plc4x.java.api.exceptions.PlcRuntimeException;
-import org.apache.plc4x.java.can.api.CANFrame;
 import org.apache.plc4x.java.can.api.conversation.canopen.CANConversation;
-import org.apache.plc4x.java.can.api.conversation.canopen.CANFrameBuilder;
-import org.apache.plc4x.java.socketcan.readwrite.SocketCANFrame;
+import org.apache.plc4x.java.can.canopen.CANOpenFrame;
+import org.apache.plc4x.java.can.canopen.CANOpenFrameBuilder;
+import org.apache.plc4x.java.can.canopen.CANOpenFrameBuilderFactory;
 import org.apache.plc4x.java.spi.ConversationContext;
 import org.apache.plc4x.java.spi.ConversationContext.SendRequestContext;
-import org.apache.plc4x.java.spi.transaction.RequestTransactionManager.RequestTransaction;
 
 import java.time.Duration;
-import java.util.function.BiConsumer;
+import java.util.function.Consumer;
 
-public class SocketCANConversation implements CANConversation<CANFrame> {
+public class SocketCANConversation implements CANConversation<CANOpenFrame> {
 
     private final int nodeId;
-    private final ConversationContext<SocketCANFrame> context;
+    private final ConversationContext<CANOpenFrame> context;
+    private final CANOpenFrameBuilderFactory factory;
 
-    public SocketCANConversation(int nodeId, ConversationContext<SocketCANFrame> context) {
+    public SocketCANConversation(int nodeId, ConversationContext<CANOpenFrame> context, CANOpenFrameBuilderFactory factory) {
         this.nodeId = nodeId;
         this.context = context;
+        this.factory = factory;
     }
 
     @Override
@@ -28,24 +28,15 @@ public class SocketCANConversation implements CANConversation<CANFrame> {
     }
 
     @Override
-    public CANFrameBuilder<CANFrame> frameBuilder() {
-        return new SocketCANFrameBuilder();
+    public CANOpenFrameBuilder createBuilder() {
+        return factory.createBuilder();
     }
 
     @Override
-    public void send(RequestTransaction transaction, CANFrame frame, BiConsumer<RequestTransaction, SendRequestContext<CANFrame>> callback) {
-        if (frame instanceof SocketCANDelegateFrame) {
-            System.out.println("-----> Sending request frame " + transaction);
-            transaction.submit(() -> {
-                ConversationContext.SendRequestContext<CANFrame> ctx = context.sendRequest(((SocketCANDelegateFrame) frame).getFrame())
-                    .expectResponse(SocketCANFrame.class, Duration.ofSeconds(10L))
-                    .unwrap(SocketCANDelegateFrame::new);
-                System.out.println("-----> Frame been sent " + transaction);
-                callback.accept(transaction, ctx);
-            });
-            return;
-        }
-        throw new PlcRuntimeException("Unsupported frame type " + frame);
+    public void send(CANOpenFrame frame, Consumer<SendRequestContext<CANOpenFrame>> callback) {
+        SendRequestContext<CANOpenFrame> ctx = context.sendRequest(frame)
+            .expectResponse(CANOpenFrame.class, Duration.ofSeconds(10L));
+        callback.accept(ctx);
     }
 
 }
diff --git a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/socketcan/SocketCANDelegateFrame.java b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/socketcan/SocketCANDelegateFrame.java
deleted file mode 100644
index 4bf323e..0000000
--- a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/socketcan/SocketCANDelegateFrame.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package org.apache.plc4x.java.can.socketcan;
-
-import org.apache.plc4x.java.can.api.CANFrame;
-import org.apache.plc4x.java.socketcan.readwrite.SocketCANFrame;
-
-public class SocketCANDelegateFrame implements CANFrame {
-
-    private final SocketCANFrame frame;
-
-    public SocketCANDelegateFrame(SocketCANFrame frame) {
-        this.frame = frame;
-    }
-
-    @Override
-    public int getIdentifier() {
-        return frame.getIdentifier();
-    }
-
-    @Override
-    public boolean getExtended() {
-        return frame.getExtended();
-    }
-
-    @Override
-    public boolean getRemote() {
-        return frame.getRemote();
-    }
-
-    @Override
-    public boolean getError() {
-        return frame.getError();
-    }
-
-    @Override
-    public byte[] getData() {
-        return frame.getData();
-    }
-
-    public SocketCANFrame getFrame() {
-        return frame;
-    }
-
-}
diff --git a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/socketcan/SocketCANFrameBuilder.java b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/socketcan/SocketCANFrameBuilder.java
deleted file mode 100644
index b13cd17..0000000
--- a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/socketcan/SocketCANFrameBuilder.java
+++ /dev/null
@@ -1,29 +0,0 @@
-package org.apache.plc4x.java.can.socketcan;
-
-import org.apache.plc4x.java.can.api.CANFrame;
-import org.apache.plc4x.java.can.api.conversation.canopen.CANFrameBuilder;
-import org.apache.plc4x.java.socketcan.readwrite.SocketCANFrame;
-
-public class SocketCANFrameBuilder implements CANFrameBuilder<CANFrame> {
-
-    private int node;
-    private byte[] data;
-
-    @Override
-    public CANFrameBuilder<CANFrame> node(int node) {
-        this.node = node;
-        return this;
-    }
-
-    @Override
-    public CANFrameBuilder<CANFrame> data(byte[] data) {
-        this.data = data;
-        return this;
-    }
-
-    @Override
-    public CANFrame build() {
-        return new SocketCANDelegateFrame(new SocketCANFrame(node, data));
-    }
-
-}
diff --git a/sandbox/test-java-can-driver/src/test/java/org/apache/plc4x/java/can/CANOpenDriverSDOIT.java b/sandbox/test-java-can-driver/src/test/java/org/apache/plc4x/java/can/CANOpenDriverSDOIT.java
new file mode 100644
index 0000000..35db6d3
--- /dev/null
+++ b/sandbox/test-java-can-driver/src/test/java/org/apache/plc4x/java/can/CANOpenDriverSDOIT.java
@@ -0,0 +1,11 @@
+package org.apache.plc4x.java.can;
+
+import org.apache.plc4x.test.driver.DriverTestsuiteRunner;
+
+class CANOpenDriverSDOIT extends DriverTestsuiteRunner {
+
+    public CANOpenDriverSDOIT() {
+        super("/testsuite/CANOpenDriverSDOIT.xml");
+    }
+
+}
diff --git a/sandbox/test-java-can-driver/src/test/java/org/apache/plc4x/java/can/field/CANOpenPDOFieldTest.java b/sandbox/test-java-can-driver/src/test/java/org/apache/plc4x/java/can/field/CANOpenPDOFieldTest.java
new file mode 100644
index 0000000..432f073
--- /dev/null
+++ b/sandbox/test-java-can-driver/src/test/java/org/apache/plc4x/java/can/field/CANOpenPDOFieldTest.java
@@ -0,0 +1,25 @@
+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.apache.plc4x.java.canopen.readwrite.types.CANOpenService;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class CANOpenPDOFieldTest {
+
+    @Test
+    public void testNodeSyntax() {
+        final CANOpenPDOField canField = CANOpenPDOField.of("RECEIVE_PDO_2:20:RECORD");
+
+        assertEquals(20, canField.getNodeId());
+        assertEquals(CANOpenService.RECEIVE_PDO_2, canField.getService());
+        assertEquals(CANOpenDataType.RECORD, canField.getCanOpenDataType());
+    }
+
+    @Test
+    public void testInvalidSyntax() {
+        assertThrows(PlcInvalidFieldException.class, () -> CANOpenPDOField.of("PDO:"));
+    }
+}
\ No newline at end of file