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:25 UTC

[plc4x] 16/22: CANopen milestone - support for segmentet SDO upload (read) requests.

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 da8abe1794caac2d5b6fa89fc8e186dc89145a74
Author: Ɓukasz Dywicki <lu...@code-house.org>
AuthorDate: Tue Oct 6 10:58:09 2020 +0200

    CANopen milestone - support for segmentet SDO upload (read) requests.
---
 .../src/main/resources/protocols/can/canopen.mspec |  40 ++++--
 .../apache/plc4x/java/can/CANOpenPlcDriver.java    |  24 ++--
 .../org/apache/plc4x/java/can/api/CANFrame.java    |  11 ++
 .../apache/plc4x/java/can/api/CANOpenFrame.java    |  66 +++++++++
 .../api/conversation/canopen/CANConversation.java  |  18 +++
 .../api/conversation/canopen/CANFrameBuilder.java  |  11 ++
 .../conversation/canopen/CANOpenConversation.java  |  75 +++++++++++
 .../canopen/CANOpenConversationBase.java           |  15 +++
 .../api/conversation/canopen/SDOConversation.java  |  43 ++++++
 .../canopen/SDODownloadConversation.java           |  97 ++++++++++++++
 .../canopen/SDOUploadConversation.java             |  98 ++++++++++++++
 .../api/segmentation/PlcSegmentationException.java |  21 +++
 .../java/can/api/segmentation/Segmentation.java    | 131 ++++++++++++++++++
 .../api/segmentation/accumulator/ByteStorage.java  |  59 ++++++++
 .../can/api/segmentation/accumulator/Storage.java} |  40 ++++--
 .../java/can/configuration/CANConfiguration.java   |   4 +-
 .../java/can/context/CANOpenDriverContext.java     |   9 ++
 .../apache/plc4x/java/can/field/CANOpenField.java  |  51 +++++++
 .../plc4x/java/can/field/CANOpenFieldHandler.java  |  58 ++++++++
 .../plc4x/java/can/field/CANOpenNMTField.java      |  58 ++++++++
 .../plc4x/java/can/field/CANOpenSDOField.java      |  89 ++++++++++++
 .../plc4x/java/can/helper/CANOpenHelper.java       |  14 ++
 .../apache/plc4x/java/can/helper/HeaderParser.java |  13 +-
 .../apache/plc4x/java/can/listener/Callback.java   |   8 ++
 .../plc4x/java/can/listener/CompositeCallback.java |  25 ++++
 .../java/can/protocol/CANOpenProtocolLogic.java    | 149 +++++++++++++++++++--
 .../protocol/segmentation/CANOpenSegmentation.java |  86 ++++++++++++
 .../java/can/socketcan/SocketCANConversation.java  |  49 +++++++
 .../java/can/socketcan/SocketCANDelegateFrame.java |  43 ++++++
 .../java/can/socketcan/SocketCANFrameBuilder.java  |  29 ++++
 .../test/java/org/apache/plc4x/java/can/Main.java  |  48 +++++++
 .../{Main.java => field/CANOpenFieldSDOTest.java}  |  24 ++--
 .../plc4x/java/can/field/CANOpenNMTFieldTest.java} |  39 +++---
 33 files changed, 1467 insertions(+), 78 deletions(-)

diff --git a/protocols/can/src/main/resources/protocols/can/canopen.mspec b/protocols/can/src/main/resources/protocols/can/canopen.mspec
index e5e0fe1..3b1e49d 100644
--- a/protocols/can/src/main/resources/protocols/can/canopen.mspec
+++ b/protocols/can/src/main/resources/protocols/can/canopen.mspec
@@ -22,17 +22,17 @@
     ['0b0001' SYNC            ['0x80',  '0x80' , 'false' ] ]
     ['0b0001' EMCY            ['0x81',  '0xFF' , 'false' ] ]
     ['0b0010' TIME            ['0x100', '0x100', 'false' ] ]
-    ['0b0011' TRANSMIT_PDO_1  ['0x181', '0x1FF', 'true'  ] ]
-    ['0b0100' RECEIVE_PDO_1   ['0x201', '0x27F', 'true'  ] ]
-    ['0b0101' TRANSMIT_PDO_2  ['0x281', '0x2FF', 'true'  ] ]
-    ['0b0110' RECEIVE_PDO_2   ['0x301', '0x37F', 'true'  ] ]
-    ['0b0111' TRANSMIT_PDO_3  ['0x381', '0x3FF', 'true'  ] ]
-    ['0b1000' RECEIVE_PDO_3   ['0x401', '0x47F', 'true'  ] ]
-    ['0b1001' TRANSMIT_PDO_4  ['0x481', '0x4FF', 'true'  ] ]
-    ['0b1010' RECEIVE_PDO_4   ['0x501', '0x57F', 'true'  ] ]
-    ['0b1011' TRANSMIT_SDO    ['0x581', '0x5FF', 'false' ] ]
-    ['0b1100' RECEIVE_SDO     ['0x601', '0x67F', 'false' ] ]
-    ['0b1110' HEARTBEAT       ['0x701', '0x77F', 'false' ] ]
+    ['0b0011' TRANSMIT_PDO_1  ['0x180', '0x1FF', 'true'  ] ]
+    ['0b0100' RECEIVE_PDO_1   ['0x200', '0x27F', 'true'  ] ]
+    ['0b0101' TRANSMIT_PDO_2  ['0x280', '0x2FF', 'true'  ] ]
+    ['0b0110' RECEIVE_PDO_2   ['0x300', '0x37F', 'true'  ] ]
+    ['0b0111' TRANSMIT_PDO_3  ['0x380', '0x3FF', 'true'  ] ]
+    ['0b1000' RECEIVE_PDO_3   ['0x400', '0x47F', 'true'  ] ]
+    ['0b1001' TRANSMIT_PDO_4  ['0x480', '0x4FF', 'true'  ] ]
+    ['0b1010' RECEIVE_PDO_4   ['0x500', '0x57F', 'true'  ] ]
+    ['0b1011' TRANSMIT_SDO    ['0x580', '0x5FF', 'false' ] ]
+    ['0b1100' RECEIVE_SDO     ['0x600', '0x67F', 'false' ] ]
+    ['0b1110' HEARTBEAT       ['0x700', '0x77F', 'false' ] ]
 ]
 
 [enum uint 8 'NMTStateRequest'
@@ -262,6 +262,7 @@
     [REAL64      ['64'] ]
 
     // compound/complex types
+    [RECORD           [ '8'] ]
     [OCTET_STRING     [ '8'] ]
     [VISIBLE_STRING   [ '8'] ]
     [UNICODE_STRING   ['16'] ]
@@ -269,7 +270,7 @@
     [TIME_DIFFERENCE  ['48'] ]
 ]
 
-[dataIo 'DataItem' [CANOpenDataType 'dataType']
+[dataIo 'DataItem' [CANOpenDataType 'dataType', int 32 'size']
     [typeSwitch 'dataType'
         ['CANOpenDataType.BOOLEAN' Boolean
             [simple bit 'value']
@@ -328,13 +329,28 @@
         ['CANOpenDataType.REAL64' Double
             [simple float 11.52 'value']
         ]
+        ['CANOpenDataType.RECORD' List
+            [array int 8 'value' length 'size']
+        ]
         ['CANOpenDataType.OCTET_STRING' String
+            [manual string 'UTF-8' 'value'
+                'STATIC_CALL("org.apache.plc4x.java.can.helper.CANOpenHelper.parseString", io, size, _type.encoding)'
+                'STATIC_CALL("org.apache.plc4x.java.can.helper.CANOpenHelper.serializeString", io, _value, _type.encoding)' '_value.length'
+            ]
         ]
         ['CANOpenDataType.VISIBLE_STRING' String
+            [manual string 'UTF-8' 'value'
+                'STATIC_CALL("org.apache.plc4x.java.can.helper.CANOpenHelper.parseString", io, size, _type.encoding)'
+                'STATIC_CALL("org.apache.plc4x.java.can.helper.CANOpenHelper.serializeString", io, _value, _type.encoding)' '_value.length'
+            ]
         ]
         //CANOpenDataType.TIME_OF_DAY' CANOpenTime
         //CANOpenDataType.TIME_DIFFERENCE' CANOpenTime
         ['CANOpenDataType.UNICODE_STRING' String
+            [manual string 'UTF-8' 'value'
+                'STATIC_CALL("org.apache.plc4x.java.can.helper.CANOpenHelper.parseString", io, size, _type.encoding)'
+                'STATIC_CALL("org.apache.plc4x.java.can.helper.CANOpenHelper.serializeString", io, _value, _type.encoding)' '_value.length'
+            ]
         ]
     ]
 ]
\ No newline at end of file
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 6acff67..a8a5f7a 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,21 +19,18 @@
 package org.apache.plc4x.java.can;
 
 import io.netty.buffer.ByteBuf;
-import io.netty.buffer.ByteBufUtil;
 import org.apache.plc4x.java.can.configuration.CANConfiguration;
-import org.apache.plc4x.java.can.context.CANDriverContext;
+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.can.protocol.CANProtocolLogic;
 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;
 import org.apache.plc4x.java.spi.connection.SingleProtocolStackConfigurer;
-import tel.schich.javacan.CanFrame;
 
-import java.util.function.Consumer;
 import java.util.function.ToIntFunction;
 
 /**
@@ -56,21 +53,32 @@ public class CANOpenPlcDriver extends GeneratedDriverBase<SocketCANFrame> {
     }
 
     @Override
+    protected boolean canRead() {
+        return true;
+    }
+
+    @Override
+    protected boolean canWrite() {
+        return true;
+    }
+
+    @Override
     protected String getDefaultTransport() {
         return "javacan";
     }
 
     @Override
-    protected CANFieldHandler getFieldHandler() {
-        return new CANFieldHandler();
+    protected CANOpenFieldHandler getFieldHandler() {
+        return new CANOpenFieldHandler();
     }
 
     @Override
     protected ProtocolStackConfigurer<SocketCANFrame> getStackConfigurer() {
         return SingleProtocolStackConfigurer.builder(SocketCANFrame.class, SocketCANFrameIO.class)
             .withProtocol(CANOpenProtocolLogic.class)
-            .withDriverContext(CANDriverContext.class)
+            .withDriverContext(CANOpenDriverContext.class)
             .withPacketSizeEstimator(CANEstimator.class)
+            .littleEndian()
             .build();
     }
 
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
new file mode 100644
index 0000000..ff1cad6
--- /dev/null
+++ b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/api/CANFrame.java
@@ -0,0 +1,11 @@
+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
new file mode 100644
index 0000000..27db3c2
--- /dev/null
+++ b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/api/CANOpenFrame.java
@@ -0,0 +1,66 @@
+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
new file mode 100644
index 0000000..6df8b0c
--- /dev/null
+++ b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/api/conversation/canopen/CANConversation.java
@@ -0,0 +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.spi.ConversationContext.SendRequestContext;
+import org.apache.plc4x.java.spi.transaction.RequestTransactionManager.RequestTransaction;
+
+public interface CANConversation<W extends CANFrame> {
+
+    CANFrameBuilder<W> frameBuilder();
+
+    void send(W frame, BiConsumer<RequestTransaction, 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
new file mode 100644
index 0000000..53a68c3
--- /dev/null
+++ b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/api/conversation/canopen/CANFrameBuilder.java
@@ -0,0 +1,11 @@
+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
new file mode 100644
index 0000000..abe6aa0
--- /dev/null
+++ b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/api/conversation/canopen/CANOpenConversation.java
@@ -0,0 +1,75 @@
+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.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.RequestTransaction;
+
+import java.util.function.BiConsumer;
+
+public class CANOpenConversation<W extends CANFrame> {
+
+    private final int node;
+    private final CANConversation<W> delegate;
+
+    public CANOpenConversation(int node, CANConversation<W> delegate) {
+        this.node = node;
+        this.delegate = delegate;
+    }
+
+    public SDOConversation<W> 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();
+        delegate.send(frame, (tx, ctx) -> {
+            SendRequestContext<CANOpenPayload> unwrap = ctx
+//                .onError((response, error) -> {
+//                    System.err.println("Unexpected frame " + response + " " + error);
+//                })
+            .unwrap(CANOpenConversation.this::deserialize);
+            callback.accept(tx, 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
new file mode 100644
index 0000000..60b87e4
--- /dev/null
+++ b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/api/conversation/canopen/CANOpenConversationBase.java
@@ -0,0 +1,15 @@
+package org.apache.plc4x.java.can.api.conversation.canopen;
+
+import org.apache.plc4x.java.api.value.PlcValue;
+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;
+import org.apache.plc4x.java.spi.generation.ReadBuffer;
+
+public abstract class CANOpenConversationBase {
+
+    protected PlcValue decodeFrom(byte[] data, CANOpenDataType type, int length) throws ParseException {
+        return DataItemIO.staticParse(new ReadBuffer(data, true), type, length);
+    }
+
+}
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
new file mode 100644
index 0000000..1447f08
--- /dev/null
+++ b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/api/conversation/canopen/SDOConversation.java
@@ -0,0 +1,43 @@
+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.canopen.readwrite.CANOpenSDORequest;
+import org.apache.plc4x.java.canopen.readwrite.CANOpenSDOResponse;
+import org.apache.plc4x.java.canopen.readwrite.IndexAddress;
+import org.apache.plc4x.java.canopen.readwrite.SDORequest;
+import org.apache.plc4x.java.canopen.readwrite.types.CANOpenDataType;
+import org.apache.plc4x.java.canopen.readwrite.types.CANOpenService;
+import org.apache.plc4x.java.spi.ConversationContext.SendRequestContext;
+import org.apache.plc4x.java.spi.transaction.RequestTransactionManager.RequestTransaction;
+
+import java.util.function.BiConsumer;
+
+public class SDOConversation<W extends CANFrame> {
+
+    private final CANOpenConversation<W> delegate;
+
+    public SDOConversation(CANOpenConversation<W> delegate) {
+        this.delegate = delegate;
+    }
+
+    public SDODownloadConversation<W> 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 void send(SDORequest request, BiConsumer<RequestTransaction, SendRequestContext<CANOpenSDOResponse>> callback) {
+        delegate.send(CANOpenService.RECEIVE_SDO, new CANOpenSDORequest(request.getCommand(), request), (tx, ctx) -> {
+            SendRequestContext<CANOpenSDOResponse> context = ctx
+//            .onError((response, error) -> {
+//                System.out.println("Unexpected frame " + response + " " + error);
+//            })
+            .only(CANOpenSDOResponse.class);
+            callback.accept(tx, 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
new file mode 100644
index 0000000..470e190
--- /dev/null
+++ b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/api/conversation/canopen/SDODownloadConversation.java
@@ -0,0 +1,97 @@
+package org.apache.plc4x.java.can.api.conversation.canopen;
+
+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.function.BiConsumer;
+
+public class SDODownloadConversation<W extends CANFrame> {
+
+    private final SDOConversation<W> delegate;
+    private final IndexAddress indexAddress;
+    private final byte[] data;
+
+    public SDODownloadConversation(SDOConversation<W> delegate, IndexAddress indexAddress, PlcValue value, CANOpenDataType type) {
+        this.delegate = delegate;
+        this.indexAddress = indexAddress;
+
+        try {
+            data = DataItemIO.staticSerialize(value, type,  null,true).getData();
+        } catch (ParseException e) {
+            throw new PlcRuntimeException("Could not serialize data", e);
+        }
+    }
+
+    public void execute(BiConsumer<PlcResponseCode, Throwable> receiver) throws PlcException {
+        if (data.length > 4) {
+            // segmented
+
+            SDOInitiateSegmentedUploadResponse size = new SDOInitiateSegmentedUploadResponse(data.length);
+            delegate.send(new SDOInitiateDownloadRequest(false, true, indexAddress, size), (tx, ctx) -> {
+                ctx.unwrap(CANOpenSDOResponse::getResponse)
+                    .check(p -> p.getCommand() == SDOResponseCommand.INITIATE_DOWNLOAD)
+                    .only(SDOInitiateDownloadResponse.class)
+                    .check(p -> indexAddress.equals(p.getAddress()))
+                    .handle(x -> {
+                        put(data, receiver, false, 0);
+                    });
+            });
+
+            return;
+        }
+
+        // expedited
+        SDOInitiateDownloadRequest rq = new SDOInitiateDownloadRequest(
+            true, true,
+            indexAddress,
+            new SDOInitiateExpeditedUploadResponse(data)
+        );
+
+        delegate.send(rq, (tx, ctx) ->
+            ctx.onError((response, error) -> {
+                System.out.println("Unexpected frame " + response + " " + error);
+            })
+            .unwrap(CANOpenSDOResponse::getResponse)
+            .check(r -> r.getCommand() == SDOResponseCommand.INITIATE_DOWNLOAD)
+            .handle(r -> {
+                System.out.println(r);
+            })
+        );
+    }
+
+    private void put(byte[] data, BiConsumer<PlcResponseCode, Throwable> 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) -> {
+            ctx.unwrap(CANOpenSDOResponse::getResponse)
+                .only(SDOSegmentDownloadResponse.class)
+                .onError((response, error) -> {
+                    System.out.println("Unexpected frame " + response + " " + error);
+                    receiver.accept(null, error);
+                })
+                .check(r -> r.getToggle() == toggle)
+                .handle(reply -> {
+                    if (offset + segment.length == data.length) {
+                        // validate offset
+                        receiver.accept(PlcResponseCode.OK, null);
+                    } 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
new file mode 100644
index 0000000..cb2d778
--- /dev/null
+++ b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/api/conversation/canopen/SDOUploadConversation.java
@@ -0,0 +1,98 @@
+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 java.util.function.BiConsumer;
+
+public class SDOUploadConversation<W extends CANFrame> extends CANOpenConversationBase {
+
+    private final SDOConversation<W> delegate;
+    private final IndexAddress address;
+    private final CANOpenDataType type;
+
+    public SDOUploadConversation(SDOConversation<W> delegate, IndexAddress address, CANOpenDataType type) {
+        this.delegate = delegate;
+        this.address = address;
+        this.type = type;
+    }
+
+    public void execute(BiConsumer<PlcValue, Throwable> receiver) throws PlcException {
+        SDOInitiateUploadRequest rq = new SDOInitiateUploadRequest(address);
+
+        delegate.send(rq, (tx, ctx) ->
+            ctx
+//            .onError((response, error) -> {
+//                System.err.println("Unexpected frame " + response + " " + error);
+//                receiver.accept(null, error);
+//            })
+            .unwrap(CANOpenSDOResponse::getResponse)
+            .onError(((response, error) -> {
+                if (response instanceof SDOAbortResponse) {
+                    SDOAbortResponse abort = (SDOAbortResponse) response;
+                    SDOAbort sdoAbort = abort.getAbort();
+                    receiver.accept(null, new PlcException("Could not read value. Remote party reported code " + sdoAbort.getCode()));
+                } else {
+                    receiver.accept(null, error);
+                }
+            }))
+            .only(SDOInitiateUploadResponse.class)
+            .check(r -> r.getAddress().equals(address))
+            .handle(response -> {
+                handle(receiver, response);
+            })
+        );
+    }
+
+    private void handle(BiConsumer<PlcValue, Throwable> receiver, SDOInitiateUploadResponse answer) {
+        BiConsumer<Integer, byte[]> valueCallback = (length, bytes) -> {
+            try {
+                receiver.accept(decodeFrom(bytes, type, length), null);
+            } catch (ParseException e) {
+                receiver.accept(null, e);
+            }
+        };
+
+        if (answer.getExpedited() && answer.getIndicated() && answer.getPayload() instanceof SDOInitiateExpeditedUploadResponse) {
+            SDOInitiateExpeditedUploadResponse payload = (SDOInitiateExpeditedUploadResponse) answer.getPayload();
+            valueCallback.accept(payload.getData().length, payload.getData());
+        } else if (answer.getPayload() instanceof SDOInitiateSegmentedUploadResponse) {
+            ByteStorage.SDOUploadStorage storage = new ByteStorage.SDOUploadStorage();
+            storage.append(answer);
+
+            SDOInitiateSegmentedUploadResponse segment = (SDOInitiateSegmentedUploadResponse) answer.getPayload();
+            fetch(storage, valueCallback, receiver, false, Long.valueOf(segment.getBytes()).intValue());
+        } else {
+            receiver.accept(null, new PlcException("Unsupported SDO operation kind."));
+        }
+    }
+
+    private void fetch(ByteStorage.SDOUploadStorage storage, BiConsumer<Integer, byte[]> valueCallback, BiConsumer<PlcValue, Throwable> receiver, boolean toggle, int size) {
+        delegate.send(new SDOSegmentUploadRequest(toggle), (tx, ctx) -> {
+            ctx.unwrap(CANOpenSDOResponse::getResponse)
+                .only(SDOSegmentUploadResponse.class)
+                .onError((response, error) -> {
+                    System.out.println("Unexpected frame " + response + " " + error);
+                    receiver.accept(null, error);
+                })
+                .check(r -> r.getToggle() == toggle)
+                .handle(reply -> {
+                    storage.append(reply);
+
+                    if (reply.getLast()) {
+                        // validate size
+                        valueCallback.accept(Long.valueOf(size).intValue(), storage.get());
+                    } else {
+                        fetch(storage, valueCallback, receiver, !toggle, size);
+                    }
+                });
+        });
+    }
+
+
+}
diff --git a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/api/segmentation/PlcSegmentationException.java b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/api/segmentation/PlcSegmentationException.java
new file mode 100644
index 0000000..1023551
--- /dev/null
+++ b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/api/segmentation/PlcSegmentationException.java
@@ -0,0 +1,21 @@
+package org.apache.plc4x.java.can.api.segmentation;
+
+import org.apache.plc4x.java.api.exceptions.PlcException;
+
+public class PlcSegmentationException extends PlcException {
+    public PlcSegmentationException(String message) {
+        super(message);
+    }
+
+    public PlcSegmentationException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public PlcSegmentationException(Throwable cause) {
+        super(cause);
+    }
+
+    public PlcSegmentationException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
+        super(message, cause, enableSuppression, writableStackTrace);
+    }
+}
diff --git a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/api/segmentation/Segmentation.java b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/api/segmentation/Segmentation.java
new file mode 100644
index 0000000..84f915d
--- /dev/null
+++ b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/api/segmentation/Segmentation.java
@@ -0,0 +1,131 @@
+/*
+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.api.segmentation;
+
+import org.apache.plc4x.java.can.api.segmentation.accumulator.Storage;
+import org.apache.plc4x.java.spi.ConversationContext;
+import org.apache.plc4x.java.spi.transaction.RequestTransactionManager;
+
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+
+/**
+ * Utility type for handling segmented request-response operations.
+ *
+ * Segmented operation is one which spans over multiple frames and contains of:
+ * - initialization
+ * - 0... n steps
+ * - finalization
+ *
+ * Depending on use case there might be 0 intermediate frames, leading to situation where requester sends segment request
+ * and answer comes in one shot.
+ */
+public abstract class Segmentation<C, T, R> {
+
+    private final Duration timeout;
+    private final Storage<T, R> storage;
+    private final Class<C> frameType;
+
+    private Supplier<T> request;
+    private Predicate<T> responseValidator;
+
+    private Predicate<T> finalStep;
+    private Consumer<List<T>> callback;
+    private Function<T, T> step;
+    private Predicate<T> stepValidator;
+    private List<T> answers = new ArrayList<>();
+
+    public Segmentation(Class<C> frameType, Duration timeout, Storage<T, R> storage) {
+        this.frameType = frameType;
+        this.timeout = timeout;
+        this.storage = storage;
+    }
+
+    public Segmentation<C, T, R> begin(Supplier<T> request, Predicate<T> requestAnswer) {
+        this.request = request;
+        this.responseValidator = requestAnswer;
+        return this;
+    }
+
+    public Segmentation<C, T, R> step(Function<T, T> step, Predicate<T> stepAnswer) {
+        this.step = step;
+        this.stepValidator = stepAnswer;
+        return this;
+    }
+
+    public Segmentation<C, T, R> end(Predicate<T> finalStep, Consumer<List<T>> callback) {
+        this.finalStep = finalStep;
+        this.callback = callback;
+        return this;
+    }
+
+    public void execute(RequestTransactionManager tm, ConversationContext<C> context) throws PlcSegmentationException {
+        Consumer<T> consumer = new Consumer<T>() {
+            @Override
+            public void accept(T payload) {
+                storage.append(payload);
+
+                if (finalStep.test(payload)) {
+                    callback.accept(answers);
+                } else {
+                    try {
+                        T apply = step.apply(payload);
+                        send(tm, context, () -> apply, stepValidator, this);
+                    } catch (PlcSegmentationException e) {
+                        throw new RuntimeException(e);
+                    }
+                }
+            }
+        };
+        try {
+            send(tm, context, request, responseValidator, consumer);
+        } catch (RuntimeException e) {
+            if (e.getCause() != null) {
+                throw new PlcSegmentationException(e.getCause());
+            }
+            throw new PlcSegmentationException(e);
+        }
+    }
+
+    private void send(RequestTransactionManager tm, ConversationContext<C> context, Supplier<T> generator, Predicate<T> predicate, Consumer<T> callback) throws PlcSegmentationException {
+        C request = wrap(generator.get());
+
+        RequestTransactionManager.RequestTransaction transaction = tm.startRequest();
+        transaction.submit(() -> context.sendRequest(request)
+            .expectResponse(frameType, timeout)
+            .unwrap(this::unwrap)
+            .check(predicate)
+            .onError((payload, error) -> transaction.endRequest())
+            .handle(payload -> {
+                callback.accept(payload);
+                transaction.endRequest();
+            })
+        );
+    }
+
+    protected abstract T unwrap(C frame);
+
+    protected abstract C wrap(T payload) throws PlcSegmentationException;
+
+}
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
new file mode 100644
index 0000000..1f143f2
--- /dev/null
+++ b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/api/segmentation/accumulator/ByteStorage.java
@@ -0,0 +1,59 @@
+package org.apache.plc4x.java.can.api.segmentation.accumulator;
+
+import org.apache.plc4x.java.canopen.readwrite.*;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Function;
+
+public class ByteStorage<T> implements Storage<T, byte[]> {
+
+    private final List<byte[]> segments = new ArrayList<>();
+    private final Function<T, byte[]> extractor;
+    private long size = 0;
+
+    public ByteStorage(Function<T, byte[]> extractor) {
+        this.extractor = extractor;
+    }
+
+    @Override
+    public void append(T frame) {
+        segments.add(extractor.apply(frame));
+        size += segments.get(segments.size() - 1).length;
+    }
+
+    public long size() {
+        return size;
+    }
+
+    @Override
+    public byte[] get() {
+        Optional<byte[]> collect = segments.stream().reduce((b1, b2) -> {
+            byte[] combined = new byte[b1.length + b2.length];
+            System.arraycopy(b1, 0, combined, 0, b1.length);
+            System.arraycopy(b2, 0, combined, b1.length, b2.length);
+            return combined;
+        });
+        return collect.orElse(new byte[0]);
+    }
+
+    public static class SDOUploadStorage extends ByteStorage<SDOResponse> {
+        public SDOUploadStorage() {
+            super((sdoResponse -> {
+                if (sdoResponse instanceof SDOSegmentUploadResponse) {
+                    return ((SDOSegmentUploadResponse) sdoResponse).getData();
+                }
+                if (sdoResponse instanceof SDOInitiateUploadResponse) {
+                    SDOInitiateUploadResponse initiate = (SDOInitiateUploadResponse) sdoResponse;
+
+                    if (initiate.getPayload() instanceof SDOInitiateExpeditedUploadResponse) {
+                        return ((SDOInitiateExpeditedUploadResponse) initiate.getPayload()).getData();
+                    }
+                }
+                return new byte[0];
+            }));
+        }
+    }
+
+}
diff --git a/sandbox/test-java-can-driver/src/test/java/org/apache/plc4x/java/can/Main.java b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/api/segmentation/accumulator/Storage.java
similarity index 57%
copy from sandbox/test-java-can-driver/src/test/java/org/apache/plc4x/java/can/Main.java
copy to sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/api/segmentation/accumulator/Storage.java
index 8212bea..45c9567 100644
--- a/sandbox/test-java-can-driver/src/test/java/org/apache/plc4x/java/can/Main.java
+++ b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/api/segmentation/accumulator/Storage.java
@@ -16,21 +16,35 @@ 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;
-
-import org.apache.plc4x.java.PlcDriverManager;
-import org.apache.plc4x.java.api.PlcConnection;
+package org.apache.plc4x.java.can.api.segmentation.accumulator;
 
 /**
- * Here we begin .. ;-)
+ * A storage which is called for each received segment.
+ *
+ * @param <T> Type of frame.
+ * @param <R> Type of result.
  */
-public class Main {
-
-    public static void main(String[] args) throws Exception {
-        PlcDriverManager driverManager = new PlcDriverManager();
-
-        PlcConnection connection = driverManager.getConnection("canopen:javacan://vcan0?nodeId=11");
-
-    }
+public interface Storage<T, R> {
+
+    /**
+     * Appends segmented frame.
+     *
+     * @param frame Segmented frame.
+     */
+    void append(T frame);
+
+    /**
+     * Gets accumulated size of stored data.
+     *
+     * @return Occupied memory in bytes.
+     */
+    long size();
+
+    /**
+     * Retrieves final result from segmented payload.
+     *
+     * @return Assembled result.
+     */
+    R get();
 
 }
diff --git a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/configuration/CANConfiguration.java b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/configuration/CANConfiguration.java
index 214794d..fcb7194 100644
--- a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/configuration/CANConfiguration.java
+++ b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/configuration/CANConfiguration.java
@@ -42,8 +42,8 @@ public class CANConfiguration implements Configuration, CANTransportConfiguratio
         return hearbeat;
     }
 
-    public void setHearbeat(boolean hearbeat) {
-        this.hearbeat = hearbeat;
+    public void setHeartbeat(boolean heartbeat) {
+        this.hearbeat = heartbeat;
     }
 
 }
diff --git a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/context/CANOpenDriverContext.java b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/context/CANOpenDriverContext.java
new file mode 100644
index 0000000..31c0028
--- /dev/null
+++ b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/context/CANOpenDriverContext.java
@@ -0,0 +1,9 @@
+package org.apache.plc4x.java.can.context;
+
+import org.apache.plc4x.java.can.listener.CompositeCallback;
+
+public class CANOpenDriverContext extends CANDriverContext {
+
+    public final static CompositeCallback CALLBACK = new CompositeCallback();
+
+}
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
new file mode 100644
index 0000000..6458848
--- /dev/null
+++ b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/field/CANOpenField.java
@@ -0,0 +1,51 @@
+/*
+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.field;
+
+import org.apache.plc4x.java.api.exceptions.PlcInvalidFieldException;
+import org.apache.plc4x.java.api.model.PlcField;
+
+import java.util.regex.Pattern;
+
+/**
+ * Generic field type which defines node address and address pattern (index/subindex).
+ */
+public abstract class CANOpenField implements PlcField {
+
+    public static final Pattern ADDRESS_PATTERN = Pattern.compile("(?:(0[xX](?<indexHex>[0-9a-fA-F]+))|(?<index>\\d+))/(?:(0[xX](?<subIndexHex>[0-9a-fA-F]+))|(?<subIndex>\\d+)):(?<canDataType>\\w+)(\\[(?<numberOfElements>\\d)])?");
+    public static final Pattern NODE_PATTERN = Pattern.compile("(?<nodeId>\\d+)");
+
+    private final int nodeId;
+
+    public CANOpenField(int nodeId) {
+        this.nodeId = nodeId;
+    }
+
+    public int getNodeId() {
+        return nodeId;
+    }
+
+    public static CANOpenField of(String addressString) throws PlcInvalidFieldException {
+        if (CANOpenSDOField.matches(addressString)) {
+            return CANOpenSDOField.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/CANOpenFieldHandler.java b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/field/CANOpenFieldHandler.java
new file mode 100644
index 0000000..cf37533
--- /dev/null
+++ b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/field/CANOpenFieldHandler.java
@@ -0,0 +1,58 @@
+/*
+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.field;
+
+import org.apache.plc4x.java.api.exceptions.PlcInvalidFieldException;
+import org.apache.plc4x.java.api.exceptions.PlcRuntimeException;
+import org.apache.plc4x.java.api.model.PlcField;
+import org.apache.plc4x.java.api.value.PlcList;
+import org.apache.plc4x.java.api.value.PlcString;
+import org.apache.plc4x.java.api.value.PlcValue;
+import org.apache.plc4x.java.spi.connection.DefaultPlcFieldHandler;
+import org.apache.plc4x.java.spi.connection.PlcFieldHandler;
+
+import java.util.Arrays;
+import java.util.stream.Collectors;
+
+public class CANOpenFieldHandler extends DefaultPlcFieldHandler implements PlcFieldHandler {
+
+    @Override
+    public PlcField createField(String fieldQuery) throws PlcInvalidFieldException {
+        return CANOpenField.of(fieldQuery);
+    }
+
+    @Override
+    public PlcValue encodeString(PlcField field, Object[] values) {
+        CANOpenSDOField coField = (CANOpenSDOField) field;
+        String[] strings = (String[]) values;
+
+        switch (coField.getCanOpenDataType()) {
+            case VISIBLE_STRING:
+            case OCTET_STRING:
+            case UNICODE_STRING:
+                if (values.length == 1) {
+                    return new PlcString(strings[0]);
+                } else {
+                    return new PlcList(Arrays.stream(strings).map(PlcString::new).collect(Collectors.toList()));
+                }
+        }
+
+        throw new PlcRuntimeException("Invalid encoder for type " + coField.getCanOpenDataType().name());
+    }
+}
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
new file mode 100644
index 0000000..83bba15
--- /dev/null
+++ b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/field/CANOpenNMTField.java
@@ -0,0 +1,58 @@
+/*
+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.field;
+
+import org.apache.plc4x.java.api.exceptions.PlcInvalidFieldException;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class CANOpenNMTField extends CANOpenField {
+
+    public static final Pattern ADDRESS_PATTERN = Pattern.compile("NMT|NMT:" + CANOpenField.NODE_PATTERN);
+
+    public CANOpenNMTField(int node) {
+        super(node);
+    }
+
+    public boolean isWildcard() {
+        return getNodeId() == 0;
+    }
+
+    public static boolean matches(String addressString) {
+        return ADDRESS_PATTERN.matcher(addressString).matches();
+    }
+
+    public static Matcher getMatcher(String addressString) throws PlcInvalidFieldException {
+        Matcher matcher = ADDRESS_PATTERN.matcher(addressString);
+        if (matcher.matches()) {
+            return matcher;
+        }
+
+        throw new PlcInvalidFieldException(addressString, ADDRESS_PATTERN);
+    }
+
+    public static CANOpenNMTField of(String addressString) {
+        Matcher matcher = getMatcher(addressString);
+        int nodeId = 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/CANOpenSDOField.java b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/field/CANOpenSDOField.java
new file mode 100644
index 0000000..86882c0
--- /dev/null
+++ b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/field/CANOpenSDOField.java
@@ -0,0 +1,89 @@
+/*
+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.field;
+
+import org.apache.plc4x.java.api.exceptions.PlcInvalidFieldException;
+import org.apache.plc4x.java.canopen.readwrite.types.CANOpenDataType;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class CANOpenSDOField extends CANOpenField {
+
+    public static final Pattern ADDRESS_PATTERN = Pattern.compile("SDO:" + CANOpenField.NODE_PATTERN + ":" + CANOpenField.ADDRESS_PATTERN);
+    private final short index;
+    private final short subIndex;
+    private final CANOpenDataType canOpenDataType;
+
+    public CANOpenSDOField(int node, short index, short subIndex, CANOpenDataType canOpenDataType) {
+        super(node);
+        this.index = index;
+        this.subIndex = subIndex;
+        this.canOpenDataType = canOpenDataType;
+    }
+
+    public short getIndex() {
+        return index;
+    }
+
+    public short getSubIndex() {
+        return subIndex;
+    }
+
+    public CANOpenDataType getCanOpenDataType() {
+        return canOpenDataType;
+    }
+
+    public static boolean matches(String addressString) {
+        return ADDRESS_PATTERN.matcher(addressString).matches();
+    }
+
+    public static Matcher getMatcher(String addressString) throws PlcInvalidFieldException {
+        Matcher matcher = ADDRESS_PATTERN.matcher(addressString);
+        if (matcher.matches()) {
+            return matcher;
+        }
+
+        throw new PlcInvalidFieldException(addressString, ADDRESS_PATTERN);
+    }
+
+    public static CANOpenSDOField of(String addressString) {
+        Matcher matcher = getMatcher(addressString);
+        int nodeId = Integer.parseInt(matcher.group("nodeId"));
+
+        short index = parseHex(matcher.group("indexHex"), matcher.group("index"));
+        short subIndex = parseHex(matcher.group("subIndexHex"), matcher.group("subIndex"));
+
+        String canDataTypeString = matcher.group("canDataType");
+        CANOpenDataType canOpenDataType = CANOpenDataType.valueOf(canDataTypeString);
+
+        //String numberOfElementsString = matcher.group("numberOfElements");
+        //Integer numberOfElements = numberOfElementsString != null ? Integer.valueOf(numberOfElementsString) : null;
+
+        return new CANOpenSDOField(nodeId, index, subIndex, canOpenDataType);
+    }
+
+    private static Short parseHex(String hex, String dec) {
+        if (hex != null) {
+            return Short.parseShort(hex, 16);
+        }
+        return Short.parseShort(dec);
+    }
+
+}
diff --git a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/helper/CANOpenHelper.java b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/helper/CANOpenHelper.java
index 1a9a346..3f1e22a 100644
--- a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/helper/CANOpenHelper.java
+++ b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/helper/CANOpenHelper.java
@@ -1,9 +1,12 @@
 package org.apache.plc4x.java.can.helper;
 
+import org.apache.plc4x.java.api.value.PlcValue;
 import org.apache.plc4x.java.canopen.readwrite.SDOInitiateExpeditedUploadResponse;
 import org.apache.plc4x.java.canopen.readwrite.SDOInitiateUploadResponsePayload;
 import org.apache.plc4x.java.canopen.readwrite.SDOSegmentUploadResponse;
 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;
 import org.apache.plc4x.java.spi.generation.WriteBuffer;
 
 import static org.apache.plc4x.java.spi.generation.StaticHelper.COUNT;
@@ -26,4 +29,15 @@ public class CANOpenHelper {
         // NOOP - a placeholder to let mspec compile
     }
 
+    public static Object parseString(ReadBuffer io, int length, String charset) {
+        return io.readString(8 * length, charset);
+    }
+
+    public static void serializeString(WriteBuffer io, PlcValue value, String charset) throws ParseException {
+        io.writeString(8, charset, value.getString());
+    }
+
+    public static byte[] parseByteArray(ReadBuffer io, Integer length) {
+        return new byte[0];
+    }
 }
diff --git a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/helper/HeaderParser.java b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/helper/HeaderParser.java
index 2ad7a1c..9e549de 100644
--- a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/helper/HeaderParser.java
+++ b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/helper/HeaderParser.java
@@ -6,6 +6,13 @@ import org.apache.plc4x.java.spi.generation.WriteBuffer;
 
 public class HeaderParser {
 
+    public static final int EFF_FLAG = 0b10000000_00000000_00000000_00000000;
+    public static final int RTR_FLAG = 0b01000000_00000000_00000000_00000000;
+    public static final int ERR_FLAG = 0b00100000_00000000_00000000_00000000;
+    public static final int SFF_MASK = 0b00000000_00000000_00000111_11111111;
+    public static final int EFF_MASK = 0b00011111_11111111_11111111_11111111;
+    public static final int ERR_MASK = EFF_MASK;
+
     public static final int EXTENDED_FRAME_FORMAT_FLAG = 0x80000000;
 
     public static final int REMOTE_TRANSMISSION_FLAG = 0x40000000;
@@ -17,10 +24,10 @@ public class HeaderParser {
     public static final int EXTENDED_FORMAT_IDENTIFIER_MASK = 0x1fffffff;
 
     public static int readIdentifier(int identifier) {
-        if ((identifier & EXTENDED_FORMAT_IDENTIFIER_MASK) == 0) {
-            return identifier & STANDARD_FORMAT_IDENTIFIER_MASK;
+        if ((isExtended(identifier))) {
+            return identifier & EXTENDED_FORMAT_IDENTIFIER_MASK;
         }
-        return identifier & EXTENDED_FORMAT_IDENTIFIER_MASK;
+        return identifier & STANDARD_FORMAT_IDENTIFIER_MASK;
     }
 
     public static void writeIdentifier(WriteBuffer buffer, SocketCANFrame frame) throws ParseException {
diff --git a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/listener/Callback.java b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/listener/Callback.java
new file mode 100644
index 0000000..74d8764
--- /dev/null
+++ b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/listener/Callback.java
@@ -0,0 +1,8 @@
+package org.apache.plc4x.java.can.listener;
+
+import org.apache.plc4x.java.socketcan.readwrite.SocketCANFrame;
+
+public interface Callback {
+    void receive(SocketCANFrame frame);
+}
+
diff --git a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/listener/CompositeCallback.java b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/listener/CompositeCallback.java
new file mode 100644
index 0000000..3ff5454
--- /dev/null
+++ b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/listener/CompositeCallback.java
@@ -0,0 +1,25 @@
+package org.apache.plc4x.java.can.listener;
+
+import org.apache.plc4x.java.socketcan.readwrite.SocketCANFrame;
+
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+public class CompositeCallback implements Callback {
+
+    private List<Callback> callbacks = new CopyOnWriteArrayList<>();
+
+    @Override
+    public void receive(SocketCANFrame frame) {
+        callbacks.forEach(callback -> callback.receive(frame));
+    }
+
+    public boolean addCallback(Callback callback) {
+        return callbacks.add(callback);
+    }
+
+    public boolean removeCallback(Callback callback) {
+        return callbacks.remove(callback);
+    }
+}
+
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 fa6dab2..b5aa34e 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
@@ -18,34 +18,58 @@ under the License.
 */
 package org.apache.plc4x.java.can.protocol;
 
+import org.apache.plc4x.java.api.messages.*;
+import org.apache.plc4x.java.api.model.PlcField;
+import org.apache.plc4x.java.api.types.PlcResponseCode;
+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.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.configuration.CANConfiguration;
-import org.apache.plc4x.java.canopen.readwrite.CANOpenHeartbeatPayload;
-import org.apache.plc4x.java.canopen.readwrite.CANOpenPayload;
+import org.apache.plc4x.java.can.context.CANOpenDriverContext;
+import org.apache.plc4x.java.can.field.CANOpenField;
+import org.apache.plc4x.java.can.field.CANOpenSDOField;
+import org.apache.plc4x.java.can.socketcan.SocketCANConversation;
+import org.apache.plc4x.java.canopen.readwrite.*;
 import org.apache.plc4x.java.canopen.readwrite.io.CANOpenHeartbeatPayloadIO;
 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.socketcan.readwrite.SocketCANFrame;
 import org.apache.plc4x.java.spi.ConversationContext;
 import org.apache.plc4x.java.spi.Plc4xProtocolBase;
 import org.apache.plc4x.java.spi.configuration.HasConfiguration;
+import org.apache.plc4x.java.spi.context.DriverContext;
 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.messages.*;
+import org.apache.plc4x.java.spi.messages.utils.ResponseItem;
 import org.apache.plc4x.java.spi.transaction.RequestTransactionManager;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import java.time.Duration;
+import java.util.HashMap;
+import java.util.Map;
 import java.util.Timer;
 import java.util.TimerTask;
+import java.util.concurrent.CompletableFuture;
 
 public class CANOpenProtocolLogic extends Plc4xProtocolBase<SocketCANFrame> implements HasConfiguration<CANConfiguration> {
 
+    private static final Duration REQUEST_TIMEOUT = Duration.ofSeconds(10L);
     private Logger logger = LoggerFactory.getLogger(CANOpenProtocolLogic.class);
 
     private CANConfiguration configuration;
     private RequestTransactionManager tm;
     private Timer heartbeat;
+    private CANOpenDriverContext canContext;
+    private CANConversation<CANFrame> conversation;
 
     @Override
     public void setConfiguration(CANConfiguration configuration) {
@@ -55,11 +79,23 @@ public class CANOpenProtocolLogic extends Plc4xProtocolBase<SocketCANFrame> impl
     }
 
     @Override
+    public void setDriverContext(DriverContext driverContext) {
+        super.setDriverContext(driverContext);
+        this.canContext = (CANOpenDriverContext) driverContext;
+
+        // Initialize Transaction Manager.
+        // Until the number of concurrent requests is successfully negotiated we set it to a
+        // maximum of only one request being able to be sent at a time. During the login process
+        // No concurrent requests can be sent anyway. It will be updated when receiving the
+        // S7ParameterSetupCommunication response.
+        this.tm = new RequestTransactionManager(1);
+    }
+
+    @Override
     public void onConnect(ConversationContext<SocketCANFrame> context) {
         try {
             if (configuration.isHeartbeat()) {
                 context.sendToWire(createFrame(new CANOpenHeartbeatPayload(NMTState.BOOTED_UP)));
-                context.fireConnected();
 
                 this.heartbeat = new Timer();
                 this.heartbeat.scheduleAtFixedRate(new TimerTask() {
@@ -73,15 +109,106 @@ public class CANOpenProtocolLogic extends Plc4xProtocolBase<SocketCANFrame> impl
                     }
                 }, 5000, 5000);
             }
+            context.fireConnected();
         } catch (ParseException e) {
             e.printStackTrace();
         }
     }
 
+    @Override
+    public void setContext(ConversationContext<SocketCANFrame> context) {
+        super.setContext(context);
+        this.conversation = new SocketCANConversation(tm, context);
+    }
+
     private SocketCANFrame createFrame(CANOpenHeartbeatPayload state) throws ParseException {
-        WriteBuffer buffer = new WriteBuffer(state.getLengthInBytes());
+        WriteBuffer buffer = new WriteBuffer(state.getLengthInBytes(), true);
         CANOpenHeartbeatPayloadIO.staticSerialize(buffer, state);
-        return new SocketCANFrame(cobId(CANOpenService.HEARTBEAT), buffer.getData());
+        return new SocketCANFrame(cobId(configuration.getNodeId(), CANOpenService.HEARTBEAT), buffer.getData());
+    }
+
+    public CompletableFuture<PlcWriteResponse> write(PlcWriteRequest writeRequest) {
+        CompletableFuture<PlcWriteResponse> response = new CompletableFuture<>();
+        if (writeRequest.getFieldNames().size() != 1) {
+            response.completeExceptionally(new IllegalArgumentException("Unsupported field"));
+            return response;
+        }
+
+        PlcField field = writeRequest.getFields().get(0);
+        if (!(field instanceof CANOpenField)) {
+            response.completeExceptionally(new IllegalArgumentException("Only CANOpenField instances are supported"));
+            return response;
+        }
+
+        if (!(field instanceof CANOpenSDOField)) {
+            response.completeExceptionally(new IllegalArgumentException("Only CANOpenSDOField instances are supported"));
+            return response;
+        };
+
+        writeInternally((InternalPlcWriteRequest) writeRequest, (CANOpenSDOField) field, response);
+        return response;
+    }
+
+    private void writeInternally(InternalPlcWriteRequest writeRequest, CANOpenSDOField field, CompletableFuture<PlcWriteResponse> response) {
+        CANOpenConversation<CANFrame> canopen = new CANOpenConversation<>(field.getNodeId(), conversation);
+
+        PlcValue writeValue = writeRequest.getPlcValues().get(0);
+
+        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();
+                Map<String, PlcResponseCode> fields = new HashMap<>();
+                fields.put(fieldName, PlcResponseCode.OK);
+                response.complete(new DefaultPlcWriteResponse(writeRequest, fields));
+            });
+        } catch (Exception e) {
+            response.completeExceptionally(e);
+        }
+    }
+
+    public CompletableFuture<PlcReadResponse> read(PlcReadRequest readRequest) {
+        CompletableFuture<PlcReadResponse> response = new CompletableFuture<>();
+        if (readRequest.getFieldNames().size() != 1) {
+            response.completeExceptionally(new IllegalArgumentException("SDO requires single field to be read"));
+            return response;
+        }
+
+        PlcField field = readRequest.getFields().get(0);
+        if (!(field instanceof CANOpenField)) {
+            response.completeExceptionally(new IllegalArgumentException("Only CANOpenField instances are supported"));
+            return response;
+        }
+
+        if (!(field instanceof CANOpenSDOField)) {
+            response.completeExceptionally(new IllegalArgumentException("Only CANOpenSDOField instances are supported"));
+            return response;
+        };
+
+        readInternally((InternalPlcReadRequest) readRequest, (CANOpenSDOField) field, response);
+        return response;
+    }
+
+    @Override
+    public CompletableFuture<PlcSubscriptionResponse> subscribe(PlcSubscriptionRequest subscriptionRequest) {
+        ((InternalPlcSubscriptionRequest) subscriptionRequest).getSubscriptionFields().get(0).getPlcSubscriptionType();
+        return super.subscribe(subscriptionRequest);
+    }
+
+    private void readInternally(InternalPlcReadRequest readRequest, CANOpenSDOField field, CompletableFuture<PlcReadResponse> response) {
+        CANOpenConversation<CANFrame> canopen = new CANOpenConversation<>(field.getNodeId(), conversation);
+
+        SDOUploadConversation<CANFrame> upload = canopen.sdo().upload(new IndexAddress(field.getIndex(), field.getSubIndex()), field.getCanOpenDataType());
+        try {
+            upload.execute((value, error) -> {
+                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));
+            });
+        } catch (Exception e) {
+            response.completeExceptionally(e);
+        }
     }
 
     @Override
@@ -89,6 +216,8 @@ public class CANOpenProtocolLogic extends Plc4xProtocolBase<SocketCANFrame> impl
         CANOpenService service = serviceId(msg.getIdentifier());
         CANOpenPayload payload = CANOpenPayloadIO.staticParse(new ReadBuffer(msg.getData()), service);
 
+        CANOpenDriverContext.CALLBACK.receive(msg);
+
         if (service != null) {
             logger.info("Decoded CANOpen {} from {}, message {}", service, Math.abs(service.getMin() - msg.getIdentifier()), payload);
         } else {
@@ -118,18 +247,18 @@ public class CANOpenProtocolLogic extends Plc4xProtocolBase<SocketCANFrame> impl
         }
     }
 
-    private int cobId(CANOpenService service) {
+    private int cobId(int nodeId, CANOpenService service) {
         // form 32 bit socketcan identifier
-        return (configuration.getNodeId() << 24) & 0xff000000 |
+        return (nodeId << 24) & 0xff000000 |
             (service.getValue() << 16 ) & 0x00ff0000;
     }
 
-    private CANOpenService serviceId(int nodeId) {
+    private CANOpenService serviceId(int cobId) {
         // form 32 bit socketcan identifier
-        CANOpenService service = CANOpenService.valueOf((byte) (nodeId >> 7));
+        CANOpenService service = CANOpenService.valueOf((byte) (cobId >> 7));
         if (service == null) {
             for (CANOpenService val : CANOpenService.values()) {
-                if (val.getMin() > nodeId && val.getMax() < nodeId) {
+                if (val.getMin() > cobId && val.getMax() < cobId) {
                     return val;
                 }
             }
diff --git a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/protocol/segmentation/CANOpenSegmentation.java b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/protocol/segmentation/CANOpenSegmentation.java
new file mode 100644
index 0000000..afea76e
--- /dev/null
+++ b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/protocol/segmentation/CANOpenSegmentation.java
@@ -0,0 +1,86 @@
+package org.apache.plc4x.java.can.protocol.segmentation;
+
+import org.apache.plc4x.java.can.api.segmentation.Segmentation;
+import org.apache.plc4x.java.can.api.segmentation.accumulator.Storage;
+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.socketcan.readwrite.SocketCANFrame;
+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 java.time.Duration;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.function.Supplier;
+
+/**
+ * A basic utility to execute segmented operations.
+ */
+public class CANOpenSegmentation<R> extends Segmentation<SocketCANFrame, CANOpenPayload, R> {
+
+    private final CANOpenService service;
+    private final int node;
+
+    public CANOpenSegmentation(CANOpenService service, int node, Storage<CANOpenPayload, R> storage) {
+        super(SocketCANFrame.class, Duration.ofSeconds(10L), storage);
+
+        this.service = service;
+        this.node = node;
+    }
+
+    public CANOpenSegmentation<R> begin(Supplier<CANOpenPayload> request, Predicate<CANOpenPayload> requestAnswer) {
+        super.begin(request, requestAnswer);
+        return this;
+    }
+
+    public CANOpenSegmentation<R> step(Function<CANOpenPayload, CANOpenPayload> step, Predicate<CANOpenPayload> stepAnswer) {
+        super.step(step, stepAnswer);
+        return this;
+    }
+
+    public CANOpenSegmentation<R> end(Predicate<CANOpenPayload> finalStep, Consumer<List<CANOpenPayload>> callback) {
+        super.end(finalStep, callback);
+        return this;
+    }
+
+    protected CANOpenPayload unwrap(SocketCANFrame frame) {
+        return unsecure(() -> CANOpenPayloadIO.staticParse(new ReadBuffer(frame.getData()), serviceId(frame.getIdentifier())));
+    }
+
+    private <T> T unsecure(Callable<T> statement) {
+        try {
+            return statement.call();
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    protected SocketCANFrame wrap(CANOpenPayload payload) {
+        try {
+            WriteBuffer io = new WriteBuffer(payload.getLengthInBytes(), true);
+            payload.getMessageIO().serialize(io, payload);
+            return new SocketCANFrame(service.getMin() + node, io.getData());
+        } catch (ParseException e) {
+            throw new RuntimeException("Could not construct segmented frame", 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/socketcan/SocketCANConversation.java b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/socketcan/SocketCANConversation.java
new file mode 100644
index 0000000..8b01c2a
--- /dev/null
+++ b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/socketcan/SocketCANConversation.java
@@ -0,0 +1,49 @@
+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.spi.ConversationContext;
+import org.apache.plc4x.java.spi.ConversationContext.SendRequestContext;
+import org.apache.plc4x.java.spi.transaction.RequestTransactionManager;
+import org.apache.plc4x.java.spi.transaction.RequestTransactionManager.RequestTransaction;
+
+import java.time.Duration;
+import java.util.function.BiConsumer;
+
+public class SocketCANConversation implements CANConversation<CANFrame> {
+
+    private final RequestTransactionManager tm;
+    private final ConversationContext<SocketCANFrame> context;
+
+    public SocketCANConversation(RequestTransactionManager tm, ConversationContext<SocketCANFrame> context) {
+        this.tm = tm;
+        this.context = context;
+    }
+
+    @Override
+    public CANFrameBuilder<CANFrame> frameBuilder() {
+        return new SocketCANFrameBuilder();
+    }
+
+    @Override
+    public void send(CANFrame frame, BiConsumer<RequestTransaction, SendRequestContext<CANFrame>> callback) {
+        if (frame instanceof SocketCANDelegateFrame) {
+            RequestTransactionManager.RequestTransaction transaction = tm.startRequest();
+
+            ConversationContext.SendRequestContext<CANFrame> ctx = context.sendRequest(((SocketCANDelegateFrame) frame).getFrame())
+                .expectResponse(SocketCANFrame.class, Duration.ofSeconds(10L))
+//                .onError((response, error) -> {
+//                    System.err.println("Unexpected frame " + response + " " + error);
+//                })
+                .unwrap(SocketCANDelegateFrame::new);
+            //return CompletableFuture.completedFuture(new SocketCANTransactionContext<>(transaction, ctx));
+            callback.accept(transaction, ctx);
+            return;
+        }
+        throw new PlcRuntimeException("Unsupported frame type " + frame);
+    }
+
+}
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
new file mode 100644
index 0000000..4bf323e
--- /dev/null
+++ b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/socketcan/SocketCANDelegateFrame.java
@@ -0,0 +1,43 @@
+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
new file mode 100644
index 0000000..b13cd17
--- /dev/null
+++ b/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/socketcan/SocketCANFrameBuilder.java
@@ -0,0 +1,29 @@
+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/Main.java b/sandbox/test-java-can-driver/src/test/java/org/apache/plc4x/java/can/Main.java
index 8212bea..f5550ae 100644
--- a/sandbox/test-java-can-driver/src/test/java/org/apache/plc4x/java/can/Main.java
+++ b/sandbox/test-java-can-driver/src/test/java/org/apache/plc4x/java/can/Main.java
@@ -20,6 +20,15 @@ package org.apache.plc4x.java.can;
 
 import org.apache.plc4x.java.PlcDriverManager;
 import org.apache.plc4x.java.api.PlcConnection;
+import org.apache.plc4x.java.api.messages.PlcReadRequest;
+import org.apache.plc4x.java.api.messages.PlcReadResponse;
+import org.apache.plc4x.java.api.messages.PlcWriteResponse;
+import org.apache.plc4x.java.can.context.CANOpenDriverContext;
+import org.apache.plc4x.java.can.listener.Callback;
+import org.apache.plc4x.java.socketcan.readwrite.SocketCANFrame;
+
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
 
 /**
  * Here we begin .. ;-)
@@ -29,8 +38,47 @@ public class Main {
     public static void main(String[] args) throws Exception {
         PlcDriverManager driverManager = new PlcDriverManager();
 
+        CANOpenDriverContext.CALLBACK.addCallback(new Callback() {
+            @Override
+            public void receive(SocketCANFrame frame) {
+                //System.err.println("Received frame " + frame);
+            }
+        });
+
         PlcConnection connection = driverManager.getConnection("canopen:javacan://vcan0?nodeId=11");
 
+        String value = "abcdef"; //UUID.randomUUID().toString();
+        CompletableFuture<? extends PlcWriteResponse> response = connection.writeRequestBuilder()
+            .addItem("foo", "SDO:13:0x2000/0x0:VISIBLE_STRING", value)
+            .build().execute();
+
+        response.whenComplete((writeReply, writeError) -> {
+            System.out.println("====================================");
+            if (writeError != null) {
+                System.out.println("Error ");
+                writeError.printStackTrace();
+            } else {
+                System.out.println("Result " + writeReply.getResponseCode("foo") + " " + value);
+
+                PlcReadRequest.Builder builder = connection.readRequestBuilder();
+                builder.addItem("foo", "SDO:13:0x2000/0x0:VISIBLE_STRING");
+                CompletableFuture<? extends PlcReadResponse> future = builder.build().execute();
+                future.whenComplete((readReply, readError) -> {
+                    System.out.println("====================================");
+                    if (readError != null) {
+                        System.out.println("Error ");
+                        readError.printStackTrace();
+                    } else {
+                        System.out.println("Result " + readReply.getString("foo"));
+                    }
+                });
+            }
+        });
+
+
+//        while (true) {
+
+//        }
     }
 
 }
diff --git a/sandbox/test-java-can-driver/src/test/java/org/apache/plc4x/java/can/Main.java b/sandbox/test-java-can-driver/src/test/java/org/apache/plc4x/java/can/field/CANOpenFieldSDOTest.java
similarity index 56%
copy from sandbox/test-java-can-driver/src/test/java/org/apache/plc4x/java/can/Main.java
copy to sandbox/test-java-can-driver/src/test/java/org/apache/plc4x/java/can/field/CANOpenFieldSDOTest.java
index 8212bea..a6ceae9 100644
--- a/sandbox/test-java-can-driver/src/test/java/org/apache/plc4x/java/can/Main.java
+++ b/sandbox/test-java-can-driver/src/test/java/org/apache/plc4x/java/can/field/CANOpenFieldSDOTest.java
@@ -16,21 +16,23 @@ 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;
+package org.apache.plc4x.java.can.field;
 
-import org.apache.plc4x.java.PlcDriverManager;
-import org.apache.plc4x.java.api.PlcConnection;
+import org.apache.plc4x.java.canopen.readwrite.types.CANOpenDataType;
+import org.junit.jupiter.api.Test;
 
-/**
- * Here we begin .. ;-)
- */
-public class Main {
+import static org.junit.jupiter.api.Assertions.assertEquals;
 
-    public static void main(String[] args) throws Exception {
-        PlcDriverManager driverManager = new PlcDriverManager();
+class CANOpenFieldSDOTest {
 
-        PlcConnection connection = driverManager.getConnection("canopen:javacan://vcan0?nodeId=11");
+    @Test
+    public void testFieldSyntax() {
+        final CANOpenSDOField canField = CANOpenSDOField.of("SDO:20:0x30/40:BOOLEAN");
 
+        assertEquals(20, canField.getNodeId());
+        assertEquals(0x30, canField.getIndex());
+        assertEquals(40, canField.getSubIndex());
+        assertEquals(CANOpenDataType.BOOLEAN, canField.getCanOpenDataType());
     }
 
-}
+}
\ No newline at end of file
diff --git a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/configuration/CANConfiguration.java b/sandbox/test-java-can-driver/src/test/java/org/apache/plc4x/java/can/field/CANOpenNMTFieldTest.java
similarity index 50%
copy from sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/configuration/CANConfiguration.java
copy to sandbox/test-java-can-driver/src/test/java/org/apache/plc4x/java/can/field/CANOpenNMTFieldTest.java
index 214794d..3f48fc1 100644
--- a/sandbox/test-java-can-driver/src/main/java/org/apache/plc4x/java/can/configuration/CANConfiguration.java
+++ b/sandbox/test-java-can-driver/src/test/java/org/apache/plc4x/java/can/field/CANOpenNMTFieldTest.java
@@ -16,34 +16,35 @@ 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.configuration;
+package org.apache.plc4x.java.can.field;
 
-import org.apache.plc4x.java.spi.configuration.Configuration;
-import org.apache.plc4x.java.spi.configuration.annotations.ConfigurationParameter;
-import org.apache.plc4x.java.transport.socketcan.CANTransportConfiguration;
+import org.apache.plc4x.java.api.exceptions.PlcInvalidFieldException;
+import org.junit.jupiter.api.Test;
 
-public class CANConfiguration implements Configuration, CANTransportConfiguration {
+import static org.junit.jupiter.api.Assertions.*;
 
-    @ConfigurationParameter
-    private int nodeId;
+class CANOpenNMTFieldTest {
 
-    @ConfigurationParameter
-    private boolean hearbeat;
+    @Test
+    public void testNodeSyntax() {
+        final CANOpenNMTField canField = CANOpenNMTField.of("NMT:20");
 
-    public int getNodeId() {
-        return nodeId;
+        assertEquals(20, canField.getNodeId());
+        assertFalse(canField.isWildcard());
     }
 
-    public void setNodeId(int nodeId) {
-        this.nodeId = nodeId;
-    }
+    @Test
+    public void testWildcardSyntax() {
+        final CANOpenNMTField canField = CANOpenNMTField.of("NMT:0");
 
-    public boolean isHeartbeat() {
-        return hearbeat;
+        assertEquals(0, canField.getNodeId());
+        assertTrue(canField.isWildcard());
     }
 
-    public void setHearbeat(boolean hearbeat) {
-        this.hearbeat = hearbeat;
+
+    @Test
+    public void testInvalidSyntax() {
+        assertThrows(PlcInvalidFieldException.class, () -> CANOpenNMTField.of("NMT:"));
     }
 
-}
+}
\ No newline at end of file