You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@plc4x.apache.org by cd...@apache.org on 2026/05/26 14:08:26 UTC

(plc4x) branch refactor/spi3 updated: refactor: Refactored the Can-related drivers to the new SPI.

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

chrisdutz pushed a commit to branch refactor/spi3
in repository https://gitbox.apache.org/repos/asf/plc4x.git


The following commit(s) were added to refs/heads/refactor/spi3 by this push:
     new e62d55fc44 refactor: Refactored the Can-related drivers to the new SPI.
e62d55fc44 is described below

commit e62d55fc44046a64f7b24fcd3fbe37e3c551edcd
Author: Christofer Dutz <ch...@c-ware.de>
AuthorDate: Tue May 26 16:08:16 2026 +0200

    refactor: Refactored the Can-related drivers to the new SPI.
---
 plc4j/drivers/can/pom.xml                          |  33 +-
 .../plc4x/java/can/adapter/CANDriverAdapter.java   | 116 -----
 .../java/can/adapter/Plc4xCANProtocolBase.java     |  36 --
 .../conversation/ConversationContextWrapper.java   |  93 ----
 .../adapter/conversation/DeferredErrorHandler.java |  41 --
 .../DeferredRequestContextWrapper.java             | 105 -----
 .../conversation/DeferredTimeoutHandler.java       |  42 --
 .../ResolvedSendRequestContextWrapper.java         |  97 ----
 .../conversation/SendRequestContextWrapper.java    | 107 -----
 .../java/can/generic/GenericCANConnection.java     | 292 ++++++++++++
 .../plc4x/java/can/generic/GenericCANDriver.java   |  87 ++--
 .../java/can/generic/GenericCANMessageCodec.java   | 106 +++++
 .../configuration/GenericCANConfiguration.java     |  10 +-
 .../generic/context/GenericCANDriverContext.java   |  24 -
 .../generic/protocol/GenericCANProtocolLogic.java  | 236 ----------
 .../protocol/GenericCANSubscriptionHandle.java     |  30 +-
 .../plc4x/java/can/generic/tag/GenericCANTag.java  |  21 +-
 .../java/can/generic/tag/GenericCANTagHandler.java |  12 +-
 .../transport/GenericCANFrameDataHandler.java      |  53 ---
 .../java/can/generic/transport/GenericFrame.java   |  37 +-
 .../java/can/generic/GenericCANDriverTest.java     | 175 -------
 .../can/generic/tag/GenericCANTagHandlerTest.java  |   2 +-
 plc4j/drivers/canopen/pom.xml                      |  28 +-
 .../plc4x/java/canopen/CANOpenConnection.java      | 509 +++++++++++++++++++++
 .../apache/plc4x/java/canopen/CANOpenDriver.java   |  87 ++++
 .../plc4x/java/canopen/CANOpenMessageCodec.java    | 127 +++++
 .../plc4x/java/canopen/CANOpenPlcDriver.java       | 140 ------
 .../{protocol => }/CANOpenSubscriptionHandle.java  |  29 +-
 .../api/conversation/canopen/CANConversation.java  |  33 --
 .../canopen/CANOpenConversationBase.java           | 124 -----
 .../canopen/SDODownloadConversation.java           | 143 ------
 .../canopen/SDOUploadConversation.java             | 132 ------
 .../api/segmentation/accumulator/ByteStorage.java  |  95 ----
 .../api/segmentation/accumulator/Storage.java      |  50 --
 .../configuration/CANOpenConfiguration.java        |  12 +-
 .../java/canopen/context/CANOpenDriverContext.java |  28 --
 .../java/canopen/conversation/CANConversation.java |  57 +++
 .../conversation/CANTransportConversation.java     |  54 ---
 .../ConversationPredicates.java}                   |  27 +-
 .../conversation/SDODownloadConversation.java      | 196 ++++++++
 .../conversation/SDOUploadConversation.java        | 192 ++++++++
 .../plc4x/java/canopen/listener/Callback.java      |  26 --
 .../canopen/protocol/CANOpenProtocolLogic.java     | 456 ------------------
 .../java/canopen/readwrite/utils/StaticHelper.java |  45 +-
 .../java/canopen/tag/CANOpenHeartbeatTag.java      |  16 -
 .../plc4x/java/canopen/tag/CANOpenNMTTag.java      |  16 -
 .../plc4x/java/canopen/tag/CANOpenPDOTag.java      |  20 -
 .../plc4x/java/canopen/tag/CANOpenSDOTag.java      |  19 -
 .../apache/plc4x/java/canopen/tag/CANOpenTag.java  |   3 +-
 .../plc4x/java/canopen/tag/CANOpenTagHandler.java  |   6 +-
 .../canopen/transport/CANOpenFrameDataHandler.java |  68 ---
 .../services/org.apache.plc4x.java.api.PlcDriver   |   2 +-
 .../plc4x/java/canopen/CANOpenDriverSDOIT.java     |  31 --
 .../plc4x/java/canopen/CANOpenPayloadTest.java     |  29 --
 .../plc4x/java/canopen/CANOpenWiresharkTest.java   |  29 --
 .../plc4x/java/canopen/CANTestTransport.java       |  63 ---
 .../java/org/apache/plc4x/java/canopen/Main.java   |  80 ----
 .../plc4x/java/canopen/ManualParserTest.java       | 115 -----
 .../canopen/transport/CANOpenFrameDataAdapter.java |  62 ---
 .../transport/IdentityCANOpenFrameBuilder.java     |  66 ---
 .../spi/buffers/bytebased/ReadBufferByteBased.java |  16 +-
 .../buffers/bytebased/encoding/EncodingASCII.java  |  30 ++
 plc4j/transports/socketcan/pom.xml                 |  37 +-
 .../java/socketcan/readwrite/SocketCANFrame.java   | 395 ++++++----------
 .../src/main/resources/protocols/can/canopen.mspec |  38 +-
 .../main/resources/protocols/can/socketcan.mspec   |   2 +-
 66 files changed, 2008 insertions(+), 3480 deletions(-)

diff --git a/plc4j/drivers/can/pom.xml b/plc4j/drivers/can/pom.xml
index aecaba796f..4a5cd111c9 100644
--- a/plc4j/drivers/can/pom.xml
+++ b/plc4j/drivers/can/pom.xml
@@ -37,9 +37,6 @@
   </properties>
 
   <build>
-    <!-- Override the path of the sources roots until the code is ported  -->
-    <sourceDirectory>src/main/temp</sourceDirectory>
-    <testSourceDirectory>src/test/temp</testSourceDirectory>
     <plugins>
       <plugin>
         <groupId>org.apache.karaf.tooling</groupId>
@@ -124,6 +121,36 @@
       <artifactId>plc4j-spi-values</artifactId>
       <version>0.14.0-SNAPSHOT</version>
     </dependency>
+    <dependency>
+      <groupId>org.apache.plc4x</groupId>
+      <artifactId>plc4j-spi-buffers-byte</artifactId>
+      <version>0.14.0-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.plc4x</groupId>
+      <artifactId>plc4j-spi-config</artifactId>
+      <version>0.14.0-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.plc4x</groupId>
+      <artifactId>plc4j-spi-drivers</artifactId>
+      <version>0.14.0-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.plc4x</groupId>
+      <artifactId>plc4j-transports-api</artifactId>
+      <version>0.14.0-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.plc4x</groupId>
+      <artifactId>plc4j-transports-can</artifactId>
+      <version>0.14.0-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.plc4x</groupId>
+      <artifactId>plc4j-utils-audit-log-api</artifactId>
+      <version>0.14.0-SNAPSHOT</version>
+    </dependency>
 
     <dependency>
       <groupId>org.apache.plc4x</groupId>
diff --git a/plc4j/drivers/can/src/main/java/org/apache/plc4x/java/can/adapter/CANDriverAdapter.java b/plc4j/drivers/can/src/main/java/org/apache/plc4x/java/can/adapter/CANDriverAdapter.java
deleted file mode 100644
index bbffecdb60..0000000000
--- a/plc4j/drivers/can/src/main/java/org/apache/plc4x/java/can/adapter/CANDriverAdapter.java
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * 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
- *
- *   https://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.adapter;
-
-import java.util.concurrent.CompletableFuture;
-import java.util.function.Function;
-
-import org.apache.plc4x.java.api.messages.PlcReadRequest;
-import org.apache.plc4x.java.api.messages.PlcReadResponse;
-import org.apache.plc4x.java.api.messages.PlcSubscriptionRequest;
-import org.apache.plc4x.java.api.messages.PlcSubscriptionResponse;
-import org.apache.plc4x.java.api.messages.PlcUnsubscriptionRequest;
-import org.apache.plc4x.java.api.messages.PlcUnsubscriptionResponse;
-import org.apache.plc4x.java.api.messages.PlcWriteRequest;
-import org.apache.plc4x.java.api.messages.PlcWriteResponse;
-import org.apache.plc4x.java.can.adapter.conversation.ConversationContextWrapper;
-import org.apache.plc4x.java.spi.ConversationContext;
-import org.apache.plc4x.java.spi.Plc4xProtocolBase;
-import org.apache.plc4x.java.spi.connection.PlcTagHandler;
-import org.apache.plc4x.java.spi.context.DriverContext;
-import org.apache.plc4x.java.transport.can.CANTransport.FrameHandler;
-import org.apache.plc4x.java.transport.can.FrameData;
-
-public class CANDriverAdapter<C, T> extends Plc4xProtocolBase<C> {
-
-    private final Plc4xCANProtocolBase<T> delegate;
-    private final Class<C> wireType;
-    private final Function<C, FrameData> adapter;
-    private final FrameHandler<C, T> frameHandler;
-    private final PlcTagHandler tagHandler;
-
-    public CANDriverAdapter(Plc4xCANProtocolBase<T> delegate, Class<C> wireType, Function<C, FrameData> adapter, FrameHandler<C, T> frameHandler, PlcTagHandler tagHandler) {
-        this.delegate = delegate;
-        this.wireType = wireType;
-        this.adapter = adapter;
-        this.frameHandler = frameHandler;
-        this.tagHandler = tagHandler;
-    }
-
-    @Override
-    public void setDriverContext(DriverContext driverContext) {
-        delegate.setDriverContext(driverContext);
-    }
-
-    @Override
-    public DriverContext getDriverContext() {
-        return delegate.getDriverContext();
-    }
-
-    @Override
-    public void setConversationContext(ConversationContext<C> conversationContext) {
-        delegate.setConversationContext(new ConversationContextWrapper<>(conversationContext, wireType, adapter, frameHandler, conversationContext.getAuthentication()));
-    }
-
-    @Override
-    public PlcTagHandler getTagHandler() {
-        return tagHandler;
-    }
-
-    @Override
-    public void onConnect(ConversationContext<C> context) {
-        delegate.onConnect(new ConversationContextWrapper<>(context, wireType, adapter, frameHandler, context.getAuthentication()));
-    }
-
-    @Override
-    public void onDisconnect(ConversationContext<C> context) {
-        delegate.onDisconnect(new ConversationContextWrapper<>(context, wireType, adapter, frameHandler, context.getAuthentication()));
-    }
-
-    @Override
-    protected void decode(ConversationContext<C> context, C msg) throws Exception {
-        delegate.decode(new ConversationContextWrapper<>(context, wireType, adapter, frameHandler, context.getAuthentication()), frameHandler.fromCAN(adapter.apply(msg)));
-    }
-
-    @Override
-    public CompletableFuture<PlcReadResponse> read(PlcReadRequest readRequest) {
-        return delegate.read(readRequest);
-    }
-
-    @Override
-    public CompletableFuture<PlcWriteResponse> write(PlcWriteRequest writeRequest) {
-        return delegate.write(writeRequest);
-    }
-
-    @Override
-    public CompletableFuture<PlcSubscriptionResponse> subscribe(PlcSubscriptionRequest subscriptionRequest) {
-        return delegate.subscribe(subscriptionRequest);
-    }
-
-    @Override
-    public CompletableFuture<PlcUnsubscriptionResponse> unsubscribe(PlcUnsubscriptionRequest unsubscriptionRequest) {
-        return delegate.unsubscribe(unsubscriptionRequest);
-    }
-
-    @Override
-    public void close(ConversationContext<C> context) {
-        delegate.close(new ConversationContextWrapper<>(context, wireType, adapter, frameHandler, context.getAuthentication()));
-    }
-
-}
diff --git a/plc4j/drivers/can/src/main/java/org/apache/plc4x/java/can/adapter/Plc4xCANProtocolBase.java b/plc4j/drivers/can/src/main/java/org/apache/plc4x/java/can/adapter/Plc4xCANProtocolBase.java
deleted file mode 100644
index aca134ae8a..0000000000
--- a/plc4j/drivers/can/src/main/java/org/apache/plc4x/java/can/adapter/Plc4xCANProtocolBase.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * 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
- *
- *   https://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.adapter;
-
-import org.apache.plc4x.java.spi.ConversationContext;
-import org.apache.plc4x.java.spi.Plc4xProtocolBase;
-
-/**
- * Small parent to declare base type for CAN protocols.
- *
- * @param <T> Type of wire message.
- */
-public abstract class Plc4xCANProtocolBase<T> extends Plc4xProtocolBase<T> {
-
-    @Override
-    public void decode(ConversationContext<T> context, T msg) throws Exception {
-        super.decode(context, msg);
-    }
-
-}
diff --git a/plc4j/drivers/can/src/main/java/org/apache/plc4x/java/can/adapter/conversation/ConversationContextWrapper.java b/plc4j/drivers/can/src/main/java/org/apache/plc4x/java/can/adapter/conversation/ConversationContextWrapper.java
deleted file mode 100644
index adfbe413bb..0000000000
--- a/plc4j/drivers/can/src/main/java/org/apache/plc4x/java/can/adapter/conversation/ConversationContextWrapper.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * 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
- *
- *   https://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.adapter.conversation;
-
-import io.netty.channel.Channel;
-import java.time.Duration;
-import java.util.function.Function;
-
-import org.apache.plc4x.java.api.authentication.PlcAuthentication;
-import org.apache.plc4x.java.spi.configuration.PlcConnectionConfiguration;
-import org.apache.plc4x.java.spi.ConversationContext;
-import org.apache.plc4x.java.transport.can.CANTransport.FrameHandler;
-import org.apache.plc4x.java.transport.can.FrameData;
-
-public class ConversationContextWrapper<C, T> implements ConversationContext<T> {
-
-    private final ConversationContext<C> delegate;
-    private final Class<C> wireType;
-    private final Function<C, FrameData> adapter;
-    private final FrameHandler<C, T> frameHandler;
-
-    private final PlcAuthentication authentication;
-
-    public ConversationContextWrapper(ConversationContext<C> delegate, Class<C> wireType, Function<C, FrameData> adapter, FrameHandler<C, T> frameHandler, PlcAuthentication authentication) {
-        this.delegate = delegate;
-        this.wireType = wireType;
-        this.adapter = adapter;
-        this.frameHandler = frameHandler;
-        this.authentication = authentication;
-    }
-
-    @Override
-    public PlcAuthentication getAuthentication() {
-        return authentication;
-    }
-
-    @Override
-    public Channel getChannel() {
-        return delegate.getChannel();
-    }
-
-    @Override
-    public boolean isPassive() {
-        return delegate.isPassive();
-    }
-
-    @Override
-    public void sendToWire(T msg) {
-        delegate.sendToWire(frameHandler.toCAN(msg));
-    }
-
-    @Override
-    public void fireConnected() {
-        delegate.fireConnected();
-    }
-
-    @Override
-    public void fireDisconnected() {
-        delegate.fireDisconnected();
-    }
-
-    @Override
-    public void fireDiscovered(PlcConnectionConfiguration c) {
-        delegate.fireDiscovered(c);
-    }
-
-    @Override
-    public SendRequestContext<T> sendRequest(T packet) {
-        return new SendRequestContextWrapper<>("", delegate.sendRequest(frameHandler.toCAN(packet)), wireType, adapter, frameHandler);
-    }
-
-    @Override
-    public ExpectRequestContext<T> expectRequest(Class<T> clazz, Duration timeout) {
-        throw new UnsupportedOperationException();
-    }
-
-}
diff --git a/plc4j/drivers/can/src/main/java/org/apache/plc4x/java/can/adapter/conversation/DeferredErrorHandler.java b/plc4j/drivers/can/src/main/java/org/apache/plc4x/java/can/adapter/conversation/DeferredErrorHandler.java
deleted file mode 100644
index 60c15ea761..0000000000
--- a/plc4j/drivers/can/src/main/java/org/apache/plc4x/java/can/adapter/conversation/DeferredErrorHandler.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * 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
- *
- *   https://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.adapter.conversation;
-
-import java.util.function.BiConsumer;
-
-public class DeferredErrorHandler<C, E extends Throwable> implements BiConsumer<C, E> {
-
-    private BiConsumer<?, E> errorHandler;
-
-    public DeferredErrorHandler(BiConsumer<?, E> errorHandler) {
-        this.errorHandler = errorHandler;
-    }
-
-    @Override
-    public void accept(C frame, E error) {
-        if (errorHandler != null) {
-            errorHandler.accept(null, error);
-        }
-    }
-
-    public void setHandler(BiConsumer<?, E> consumer) {
-        errorHandler = consumer;
-    }
-}
diff --git a/plc4j/drivers/can/src/main/java/org/apache/plc4x/java/can/adapter/conversation/DeferredRequestContextWrapper.java b/plc4j/drivers/can/src/main/java/org/apache/plc4x/java/can/adapter/conversation/DeferredRequestContextWrapper.java
deleted file mode 100644
index c7c3175426..0000000000
--- a/plc4j/drivers/can/src/main/java/org/apache/plc4x/java/can/adapter/conversation/DeferredRequestContextWrapper.java
+++ /dev/null
@@ -1,105 +0,0 @@
-/*
- * 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
- *
- *   https://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.adapter.conversation;
-
-import java.time.Duration;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.TimeoutException;
-import java.util.function.BiConsumer;
-import java.util.function.Consumer;
-import java.util.function.Function;
-import java.util.function.Predicate;
-import org.apache.plc4x.java.spi.ConversationContext.ContextHandler;
-import org.apache.plc4x.java.spi.ConversationContext.SendRequestContext;
-
-public class DeferredRequestContextWrapper<T> implements SendRequestContext<T> {
-
-    private String name;
-
-    private final SendRequestContext<T> delegate;
-    private final Function<SendRequestContext<?>, SendRequestContext<?>> completer;
-    protected final Consumer<TimeoutException> onTimeoutConsumer;
-    protected final BiConsumer<?, ? extends Throwable> errorConsumer;
-
-    public DeferredRequestContextWrapper(String name, SendRequestContext<T> delegate, Function<SendRequestContext<?>, SendRequestContext<?>> completer,
-        Consumer<TimeoutException> onTimeoutConsumer, BiConsumer<?, ? extends Throwable> errorConsumer) {
-        this.name = name;
-        this.delegate = delegate;
-        this.completer = completer;
-        this.onTimeoutConsumer = onTimeoutConsumer;
-        this.errorConsumer = errorConsumer;
-    }
-
-    @Override
-    public SendRequestContext<T> name(String name) {
-        this.name = name;
-        return this;
-    }
-
-    @Override
-    public SendRequestContext<T> expectResponse(Class<T> clazz, Duration timeout) {
-        throw new IllegalStateException("Response type is already specified");
-    }
-
-    @Override
-    public SendRequestContext<T> check(Predicate<T> checker) {
-        delegate.check(checker);
-        return this;
-    }
-
-    @Override
-    public ContextHandler handle(Consumer<T> packetConsumer) {
-        return delegate.handle(packetConsumer);
-    }
-
-    @Override
-    public CompletableFuture<T> toFuture() {
-        CompletableFuture<T> future = new CompletableFuture<>();
-        handle(future::complete);
-        return future;
-    }
-
-    @Override
-    public SendRequestContext<T> onTimeout(Consumer<TimeoutException> packetConsumer) {
-        return new DeferredRequestContextWrapper<>(name, delegate, completer, packetConsumer, errorConsumer);
-    }
-
-    @Override
-    public <E extends Throwable> SendRequestContext<T> onError(BiConsumer<T, E> packetConsumer) {
-        return new DeferredRequestContextWrapper<>(name, delegate, completer, onTimeoutConsumer, errorConsumer);
-    }
-
-    @Override
-    public <R> SendRequestContext<R> unwrap(Function<T, R> unwrapper) {
-        return resolve(new DeferredRequestContextWrapper<>(name, delegate.unwrap(unwrapper), completer, onTimeoutConsumer, errorConsumer));
-    }
-
-    private <R> SendRequestContext<R> resolve(DeferredRequestContextWrapper<R> contextWrapper) {
-        if (contextWrapper.errorConsumer != null && contextWrapper.onTimeoutConsumer != null) {
-            return (SendRequestContext<R>) contextWrapper.completer.apply(contextWrapper);
-        }
-        return contextWrapper;
-    }
-
-    @Override
-    public <R> SendRequestContext<R> only(Class<R> clazz) {
-        return resolve(new DeferredRequestContextWrapper<>(name, delegate.only(clazz), completer, onTimeoutConsumer, errorConsumer));
-    }
-
-}
diff --git a/plc4j/drivers/can/src/main/java/org/apache/plc4x/java/can/adapter/conversation/DeferredTimeoutHandler.java b/plc4j/drivers/can/src/main/java/org/apache/plc4x/java/can/adapter/conversation/DeferredTimeoutHandler.java
deleted file mode 100644
index 88c45d8315..0000000000
--- a/plc4j/drivers/can/src/main/java/org/apache/plc4x/java/can/adapter/conversation/DeferredTimeoutHandler.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * 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
- *
- *   https://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.adapter.conversation;
-
-import java.util.concurrent.TimeoutException;
-import java.util.function.Consumer;
-
-public class DeferredTimeoutHandler<T> implements Consumer<TimeoutException> {
-
-    private Consumer<TimeoutException> delegate;
-
-    public DeferredTimeoutHandler(Consumer<TimeoutException> delegate) {
-        this.delegate = delegate;
-    }
-
-    @Override
-    public void accept(TimeoutException e) {
-        if (delegate != null) {
-            delegate.accept(e);
-        }
-    }
-
-    public void setHandler(Consumer<TimeoutException> delegate) {
-        this.delegate = delegate;
-    }
-}
diff --git a/plc4j/drivers/can/src/main/java/org/apache/plc4x/java/can/adapter/conversation/ResolvedSendRequestContextWrapper.java b/plc4j/drivers/can/src/main/java/org/apache/plc4x/java/can/adapter/conversation/ResolvedSendRequestContextWrapper.java
deleted file mode 100644
index 5fe07e3342..0000000000
--- a/plc4j/drivers/can/src/main/java/org/apache/plc4x/java/can/adapter/conversation/ResolvedSendRequestContextWrapper.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * 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
- *
- *   https://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.adapter.conversation;
-
-import java.time.Duration;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.TimeoutException;
-import java.util.function.BiConsumer;
-import java.util.function.Consumer;
-import java.util.function.Function;
-import java.util.function.Predicate;
-import org.apache.plc4x.java.spi.ConversationContext.ContextHandler;
-import org.apache.plc4x.java.spi.ConversationContext.SendRequestContext;
-
-public class ResolvedSendRequestContextWrapper<T> implements SendRequestContext<T> {
-
-    private String name;
-
-    private final SendRequestContext<T> delegate;
-    private final DeferredErrorHandler<?, ?> errorHandler;
-    private final DeferredTimeoutHandler<?> timeoutHandler;
-
-    public ResolvedSendRequestContextWrapper(String name, SendRequestContext<T> delegate, DeferredErrorHandler<?, ?> errorHandler,
-        DeferredTimeoutHandler<?> timeoutHandler) {
-        this.name = name;
-        this.delegate = delegate;
-        this.errorHandler = errorHandler;
-        this.timeoutHandler = timeoutHandler;
-    }
-
-    @Override
-    public SendRequestContext<T> name(String name) {
-        this.name = name;
-        return this;
-    }
-
-    @Override
-    public SendRequestContext<T> expectResponse(Class<T> clazz, Duration timeout) {
-        throw new IllegalStateException("Response type is already specified");
-    }
-
-    @Override
-    public SendRequestContext<T> check(Predicate<T> checker) {
-        delegate.check(checker);
-        return this;
-    }
-
-    @Override
-    public ContextHandler handle(Consumer<T> packetConsumer) {
-        return delegate.handle(packetConsumer);
-    }
-
-    @Override
-    public CompletableFuture<T> toFuture() {
-        CompletableFuture<T> future = new CompletableFuture<>();
-        handle(future::complete);
-        return future;
-    }
-
-    @Override
-    public SendRequestContext<T> onTimeout(Consumer<TimeoutException> packetConsumer) {
-        timeoutHandler.setHandler(packetConsumer);
-        return this;
-    }
-
-    @Override
-    public <E extends Throwable> SendRequestContext<T> onError(BiConsumer<T, E> packetConsumer) {
-        errorHandler.setHandler((BiConsumer) packetConsumer);
-        return this;
-    }
-
-    @Override
-    public <R> SendRequestContext<R> unwrap(Function<T, R> unwrapper) {
-        return new ResolvedSendRequestContextWrapper<>(name, delegate.unwrap(unwrapper), errorHandler, timeoutHandler);
-    }
-
-    @Override
-    public <R> SendRequestContext<R> only(Class<R> clazz) {
-        return new ResolvedSendRequestContextWrapper<>(name, delegate.only(clazz), errorHandler, timeoutHandler);
-    }
-}
diff --git a/plc4j/drivers/can/src/main/java/org/apache/plc4x/java/can/adapter/conversation/SendRequestContextWrapper.java b/plc4j/drivers/can/src/main/java/org/apache/plc4x/java/can/adapter/conversation/SendRequestContextWrapper.java
deleted file mode 100644
index bba458ddab..0000000000
--- a/plc4j/drivers/can/src/main/java/org/apache/plc4x/java/can/adapter/conversation/SendRequestContextWrapper.java
+++ /dev/null
@@ -1,107 +0,0 @@
-/*
- * 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
- *
- *   https://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.adapter.conversation;
-
-import java.time.Duration;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.TimeoutException;
-import java.util.function.BiConsumer;
-import java.util.function.Consumer;
-import java.util.function.Function;
-import java.util.function.Predicate;
-import org.apache.plc4x.java.spi.ConversationContext.ContextHandler;
-import org.apache.plc4x.java.spi.ConversationContext.SendRequestContext;
-import org.apache.plc4x.java.transport.can.CANTransport.FrameHandler;
-import org.apache.plc4x.java.transport.can.FrameData;
-
-public class SendRequestContextWrapper<C, T> implements SendRequestContext<T> {
-
-    private String name;
-
-    private final SendRequestContext<C> delegate;
-    private final Class<C> wireType;
-    private final Function<C, FrameData> adapter;
-    private final FrameHandler<C, T> frameHandler;
-
-    public SendRequestContextWrapper(String name, SendRequestContext<C> delegate, Class<C> wireType, Function<C, FrameData> adapter, FrameHandler<C, T> frameHandler) {
-        this.name = name;
-        this.delegate = delegate;
-        this.wireType = wireType;
-        this.adapter = adapter;
-        this.frameHandler = frameHandler;
-    }
-
-    @Override
-    public SendRequestContext<T> name(String name) {
-        this.name = name;
-        return this;
-    }
-
-    @Override
-    public SendRequestContext<T> expectResponse(Class<T> clazz, Duration timeout) {
-        DeferredErrorHandler<C, ?> errorHandler = new DeferredErrorHandler<>(null);
-        DeferredTimeoutHandler<?> timeoutHandler = new DeferredTimeoutHandler<>(null);
-        return new ResolvedSendRequestContextWrapper<>(name,
-            delegate.onError(errorHandler)
-                .onTimeout(timeoutHandler)
-                .expectResponse(wireType, timeout)
-                .unwrap(adapter)
-                .unwrap(frameHandler::fromCAN)
-                .check(f -> clazz.isAssignableFrom(f.getClass())
-            ), errorHandler, timeoutHandler
-        );
-    }
-
-    @Override
-    public SendRequestContext<T> check(Predicate<T> checker) {
-        throw new IllegalStateException("Not implemented");
-    }
-
-    @Override
-    public ContextHandler handle(Consumer<T> packetConsumer) {
-        throw new IllegalStateException("Not implemented");
-    }
-
-    @Override
-    public CompletableFuture<T> toFuture() {
-        CompletableFuture<T> future = new CompletableFuture<>();
-        handle(future::complete);
-        return future;
-    }
-
-    @Override
-    public SendRequestContext<T> onTimeout(Consumer<TimeoutException> packetConsumer) {
-        throw new IllegalStateException("Not implemented");
-    }
-
-    @Override
-    public <E extends Throwable> SendRequestContext<T> onError(BiConsumer<T, E> packetConsumer) {
-        throw new IllegalStateException("Not implemented");
-    }
-
-    @Override
-    public <R> SendRequestContext<R> unwrap(Function<T, R> unwrapper) {
-        throw new IllegalStateException("Not implemented");
-    }
-
-    @Override
-    public <R> SendRequestContext<R> only(Class<R> clazz) {
-        throw new IllegalStateException("Not implemented");
-    }
-}
diff --git a/plc4j/drivers/can/src/main/java/org/apache/plc4x/java/can/generic/GenericCANConnection.java b/plc4j/drivers/can/src/main/java/org/apache/plc4x/java/can/generic/GenericCANConnection.java
new file mode 100644
index 0000000000..09d586d570
--- /dev/null
+++ b/plc4j/drivers/can/src/main/java/org/apache/plc4x/java/can/generic/GenericCANConnection.java
@@ -0,0 +1,292 @@
+/*
+ * 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
+ *
+ *   https://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.generic;
+
+import org.apache.plc4x.java.api.messages.PlcSubscriptionEvent;
+import org.apache.plc4x.java.api.messages.PlcSubscriptionRequest;
+import org.apache.plc4x.java.api.messages.PlcSubscriptionResponse;
+import org.apache.plc4x.java.api.messages.PlcUnsubscriptionRequest;
+import org.apache.plc4x.java.api.messages.PlcUnsubscriptionResponse;
+import org.apache.plc4x.java.api.messages.PlcWriteRequest;
+import org.apache.plc4x.java.api.messages.PlcWriteResponse;
+import org.apache.plc4x.java.api.model.PlcConsumerRegistration;
+import org.apache.plc4x.java.api.model.PlcSubscriptionHandle;
+import org.apache.plc4x.java.api.model.PlcTag;
+import org.apache.plc4x.java.api.types.PlcResponseCode;
+import org.apache.plc4x.java.api.types.PlcSubscriptionType;
+import org.apache.plc4x.java.api.value.PlcValue;
+import org.apache.plc4x.java.can.generic.configuration.GenericCANConfiguration;
+import org.apache.plc4x.java.can.generic.protocol.GenericCANSubscriptionHandle;
+import org.apache.plc4x.java.can.generic.tag.GenericCANTag;
+import org.apache.plc4x.java.can.generic.tag.GenericCANTagHandler;
+import org.apache.plc4x.java.can.generic.transport.GenericFrame;
+import org.apache.plc4x.java.genericcan.readwrite.DataItem;
+import org.apache.plc4x.java.spi.buffers.api.WithOption;
+import org.apache.plc4x.java.spi.buffers.api.exceptions.BufferException;
+import org.apache.plc4x.java.spi.buffers.bytebased.ReadBufferByteBased;
+import org.apache.plc4x.java.spi.buffers.bytebased.WithByteBasedOption;
+import org.apache.plc4x.java.spi.buffers.bytebased.WriteBufferByteBased;
+import org.apache.plc4x.java.spi.drivers.ConnectionBase;
+import org.apache.plc4x.java.spi.drivers.exceptions.MessageCodecException;
+import org.apache.plc4x.java.spi.drivers.messages.DefaultPlcConsumerRegistration;
+import org.apache.plc4x.java.spi.drivers.messages.DefaultPlcSubscriptionEvent;
+import org.apache.plc4x.java.spi.drivers.messages.DefaultPlcSubscriptionResponse;
+import org.apache.plc4x.java.spi.drivers.messages.DefaultPlcSubscriptionTag;
+import org.apache.plc4x.java.spi.drivers.messages.DefaultPlcUnsubscriptionResponse;
+import org.apache.plc4x.java.spi.drivers.messages.DefaultPlcWriteResponse;
+import org.apache.plc4x.java.spi.drivers.messages.items.DefaultPlcResponseItem;
+import org.apache.plc4x.java.spi.drivers.messages.items.PlcResponseItem;
+import org.apache.plc4x.java.spi.drivers.tags.PlcTagHandler;
+import org.apache.plc4x.java.spi.transports.api.TransportInstance;
+import org.apache.plc4x.java.spi.values.DefaultPlcValueHandler;
+import org.apache.plc4x.java.spi.values.PlcValueHandler;
+import org.apache.plc4x.java.utils.auditlog.api.AuditLog;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.time.Instant;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Consumer;
+
+/**
+ * Generic CAN connection. Direct port of the legacy {@code GenericCANProtocolLogic} +
+ * {@code CANDriverAdapter} stack to the SPI3 {@link ConnectionBase} model.
+ *
+ * <p>The adapter pattern from the old SPI (Plc4xCANProtocolBase + ConversationContext
+ * wrappers) is collapsed into this class: incoming frames come straight off the codec,
+ * outgoing writes go straight to it. Each subscription holds the list of tags it cares
+ * about for a particular CAN node id; when a matching frame arrives we decode each tag
+ * out of the payload and deliver one {@code DefaultPlcSubscriptionEvent} to every
+ * registered consumer.</p>
+ */
+public class GenericCANConnection extends ConnectionBase<GenericCANConfiguration> {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(GenericCANConnection.class);
+
+    static final WithOption[] LITTLE_ENDIAN_OPTIONS = {
+        WithByteBasedOption.WithByteOrder("LITTLE_ENDIAN"),
+        WithOption.WithUnsignedIntegerEncoding("unsigned-binary"),
+        WithOption.WithSignedIntegerEncoding("twos-complement"),
+        WithOption.WithFloatEncoding("IEEE754"),
+        WithOption.WithStringEncoding("UTF8")
+    };
+
+    private GenericCANMessageCodec messageCodec;
+    private volatile boolean connected = false;
+
+    private final Map<DefaultPlcConsumerRegistration, Consumer<PlcSubscriptionEvent>> consumers = new ConcurrentHashMap<>();
+
+    public GenericCANConnection(GenericCANConfiguration configuration,
+                                TransportInstance<?> transportInstance,
+                                AuditLog auditLog) {
+        super(configuration, transportInstance, auditLog);
+    }
+
+    @Override
+    protected PlcTagHandler getTagHandler() {
+        return new GenericCANTagHandler();
+    }
+
+    @Override
+    protected PlcValueHandler getValueHandler() {
+        return new DefaultPlcValueHandler();
+    }
+
+    @Override
+    public boolean isConnected() {
+        return connected && messageCodec != null && messageCodec.isOpen();
+    }
+
+    @Override
+    protected void onConnect() {
+        messageCodec = new GenericCANMessageCodec(transportInstance, this::handleIncomingFrame);
+        startReceiving(() -> {
+            try {
+                messageCodec.processIncomingData();
+            } catch (MessageCodecException e) {
+                LOGGER.error("Error processing incoming CAN data", e);
+            }
+        });
+        connected = true;
+    }
+
+    @Override
+    public void close() throws Exception {
+        connected = false;
+        stopReceiving();
+        if (messageCodec != null) {
+            messageCodec.close();
+        }
+        consumers.clear();
+        super.close();
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    // Incoming dispatch
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+    private void handleIncomingFrame(GenericFrame frame) {
+        for (Entry<DefaultPlcConsumerRegistration, Consumer<PlcSubscriptionEvent>> entry : consumers.entrySet()) {
+            DefaultPlcConsumerRegistration registration = entry.getKey();
+            Consumer<PlcSubscriptionEvent> consumer = entry.getValue();
+            for (PlcSubscriptionHandle handle : registration.getSubscriptionHandles()) {
+                if (!(handle instanceof GenericCANSubscriptionHandle subscription)) {
+                    continue;
+                }
+                if (!subscription.matches(frame.getNodeId())) {
+                    continue;
+                }
+                Map<String, PlcResponseItem<PlcValue>> tags = new LinkedHashMap<>();
+                ReadBufferByteBased readBuffer = new ReadBufferByteBased(frame.getData(), LITTLE_ENDIAN_OPTIONS);
+                for (Entry<String, GenericCANTag> tag : subscription.getTags().entrySet()) {
+                    try {
+                        PlcValue value = DataItem.staticParse(readBuffer, tag.getValue().getDataType(), frame.getData().length);
+                        tags.put(tag.getKey(), new DefaultPlcResponseItem<>(
+                            value != null ? PlcResponseCode.OK : PlcResponseCode.INTERNAL_ERROR, value));
+                    } catch (BufferException e) {
+                        tags.put(tag.getKey(), new DefaultPlcResponseItem<>(PlcResponseCode.INVALID_DATA, null));
+                    }
+                }
+                consumer.accept(new DefaultPlcSubscriptionEvent(Instant.now(), tags));
+            }
+        }
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    // Write
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+    @Override
+    protected CompletableFuture<PlcWriteResponse> onWrite(PlcWriteRequest writeRequest) {
+        return CompletableFuture.supplyAsync(() -> {
+            // Pack all writes for a given CAN node id into one outgoing frame
+            // — that's what the legacy driver did, and what makes sense given
+            // each CAN frame can carry up to 8 bytes of payload.
+            Map<Integer, WriteBufferByteBased> messages = new LinkedHashMap<>();
+            Map<Integer, Map<String, PlcResponseCode>> responses = new HashMap<>();
+
+            for (String tagName : writeRequest.getTagNames()) {
+                PlcTag plcTag = writeRequest.getTag(tagName);
+                if (!(plcTag instanceof GenericCANTag canTag)) {
+                    responses.computeIfAbsent(-1, n -> new HashMap<>()).put(tagName, PlcResponseCode.UNSUPPORTED);
+                    continue;
+                }
+                WriteBufferByteBased buffer = messages.computeIfAbsent(canTag.getNodeId(),
+                    n -> new WriteBufferByteBased(new byte[8], LITTLE_ENDIAN_OPTIONS));
+                Map<String, PlcResponseCode> statusMap = responses.computeIfAbsent(canTag.getNodeId(),
+                    n -> new HashMap<>());
+                PlcValue value = writeRequest.getPlcValue(tagName);
+                try {
+                    DataItem.staticSerialize(buffer, value, canTag.getDataType(), value.getLength());
+                    statusMap.put(tagName, PlcResponseCode.OK);
+                } catch (BufferException e) {
+                    statusMap.put(tagName, PlcResponseCode.INVALID_DATA);
+                }
+            }
+
+            Map<String, PlcResponseCode> codes = new LinkedHashMap<>();
+            for (Entry<Integer, WriteBufferByteBased> message : messages.entrySet()) {
+                int nodeId = message.getKey();
+                Map<String, PlcResponseCode> nodeResponses = responses.get(nodeId);
+                boolean discarded = false;
+                for (Entry<String, PlcResponseCode> entry : nodeResponses.entrySet()) {
+                    codes.put(entry.getKey(), entry.getValue());
+                    if (!discarded && entry.getValue() != PlcResponseCode.OK) {
+                        LOGGER.info("Discarding frame for node {}: a tag failed to serialize ({})",
+                            nodeId, entry.getKey());
+                        discarded = true;
+                    }
+                }
+                if (!discarded) {
+                    byte[] data = message.getValue().getBytes();
+                    try {
+                        messageCodec.sendFrame(new GenericFrame(nodeId, data));
+                    } catch (MessageCodecException e) {
+                        LOGGER.error("Failed to send CAN frame for node {}", nodeId, e);
+                        for (String tagName : nodeResponses.keySet()) {
+                            codes.put(tagName, PlcResponseCode.REMOTE_ERROR);
+                        }
+                    }
+                }
+            }
+            // Tags with no node (UNSUPPORTED) need to land in the response too.
+            Map<String, PlcResponseCode> unsupported = responses.get(-1);
+            if (unsupported != null) {
+                codes.putAll(unsupported);
+            }
+
+            return new DefaultPlcWriteResponse(writeRequest, codes);
+        });
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    // Subscribe / consumer registry
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+    @Override
+    protected CompletableFuture<PlcSubscriptionResponse> onSubscribe(PlcSubscriptionRequest request) {
+        Map<String, PlcResponseItem<PlcSubscriptionHandle>> answers = new LinkedHashMap<>();
+        Map<Integer, GenericCANSubscriptionHandle> handles = new LinkedHashMap<>();
+        for (String key : request.getTagNames()) {
+            DefaultPlcSubscriptionTag subscription = (DefaultPlcSubscriptionTag) request.getTag(key);
+            if (subscription.getPlcSubscriptionType() != PlcSubscriptionType.EVENT) {
+                answers.put(key, new DefaultPlcResponseItem<>(PlcResponseCode.UNSUPPORTED, null));
+            } else if (subscription.getTag() instanceof GenericCANTag canTag) {
+                GenericCANSubscriptionHandle handle = handles.computeIfAbsent(canTag.getNodeId(),
+                    node -> new GenericCANSubscriptionHandle(this, node));
+                handle.add(key, canTag);
+                answers.put(key, new DefaultPlcResponseItem<>(PlcResponseCode.OK, handle));
+            } else {
+                answers.put(key, new DefaultPlcResponseItem<>(PlcResponseCode.INVALID_ADDRESS, null));
+            }
+        }
+        return CompletableFuture.completedFuture(new DefaultPlcSubscriptionResponse(request, answers));
+    }
+
+    @Override
+    protected CompletableFuture<PlcUnsubscriptionResponse> onUnsubscribe(PlcUnsubscriptionRequest request) {
+        // CAN is push-only — there's nothing to unsubscribe on the bus side.
+        // Consumer cleanup happens via onUnregisterConsumer.
+        return CompletableFuture.completedFuture(new DefaultPlcUnsubscriptionResponse(request));
+    }
+
+    @Override
+    protected PlcConsumerRegistration onRegisterConsumer(Consumer<PlcSubscriptionEvent> consumer,
+                                                        Collection<PlcSubscriptionHandle> handles) {
+        DefaultPlcConsumerRegistration registration = new DefaultPlcConsumerRegistration(this, consumer,
+            handles.toArray(new PlcSubscriptionHandle[0]));
+        consumers.put(registration, consumer);
+        return registration;
+    }
+
+    @Override
+    protected void onUnregisterConsumer(PlcConsumerRegistration registration) {
+        if (registration instanceof DefaultPlcConsumerRegistration r) {
+            consumers.remove(r);
+        }
+    }
+
+}
diff --git a/plc4j/drivers/can/src/main/java/org/apache/plc4x/java/can/generic/GenericCANDriver.java b/plc4j/drivers/can/src/main/java/org/apache/plc4x/java/can/generic/GenericCANDriver.java
index 2c7c1413f9..896a695ab8 100644
--- a/plc4j/drivers/can/src/main/java/org/apache/plc4x/java/can/generic/GenericCANDriver.java
+++ b/plc4j/drivers/can/src/main/java/org/apache/plc4x/java/can/generic/GenericCANDriver.java
@@ -18,31 +18,26 @@
  */
 package org.apache.plc4x.java.can.generic;
 
-import org.apache.plc4x.java.can.generic.tag.GenericCANTagHandler;
-import org.apache.plc4x.java.spi.configuration.PlcConnectionConfiguration;
-import org.apache.plc4x.java.api.exceptions.PlcRuntimeException;
-import org.apache.plc4x.java.can.adapter.CANDriverAdapter;
+import org.apache.plc4x.java.api.model.PlcTag;
 import org.apache.plc4x.java.can.generic.configuration.GenericCANConfiguration;
-import org.apache.plc4x.java.can.generic.context.GenericCANDriverContext;
-import org.apache.plc4x.java.can.generic.protocol.GenericCANProtocolLogic;
-import org.apache.plc4x.java.can.generic.transport.GenericCANFrameDataHandler;
-import org.apache.plc4x.java.spi.configuration.ConfigurationFactory;
-import org.apache.plc4x.java.spi.connection.CustomProtocolStackConfigurer;
-import org.apache.plc4x.java.spi.connection.GeneratedDriverBase;
-import org.apache.plc4x.java.spi.connection.ProtocolStackConfigurer;
-import org.apache.plc4x.java.spi.generation.Message;
-import org.apache.plc4x.java.spi.optimizer.BaseOptimizer;
-import org.apache.plc4x.java.spi.transport.Transport;
-import org.apache.plc4x.java.transport.can.CANTransport;
-
+import org.apache.plc4x.java.can.generic.tag.GenericCANTag;
+import org.apache.plc4x.java.spi.config.Configuration;
+import org.apache.plc4x.java.spi.drivers.ConnectionBase;
+import org.apache.plc4x.java.spi.drivers.DriverBase;
+import org.apache.plc4x.java.spi.transports.api.TransportInstance;
+import org.apache.plc4x.java.utils.auditlog.api.AuditLog;
+
+import java.util.List;
 import java.util.Optional;
 
 /**
- * A generic purpose CAN driver which is able to work with any compatible CAN transport.
+ * Generic CAN driver, transport-agnostic so long as the chosen transport
+ * presents incoming traffic as fixed 16-byte SocketCAN-style frames (true for
+ * both {@code can-socketcan} and {@code can-virtualcan}).
  *
- * Main role of this driver is provisioning of quick and easy way to create user specific CAN bus applications.
+ * <p>URL: {@code genericcan:&lt;transport&gt;://...}</p>
  */
-public class GenericCANDriver extends GeneratedDriverBase<Message> {
+public class GenericCANDriver extends DriverBase {
 
     @Override
     public String getProtocolCode() {
@@ -55,18 +50,18 @@ public class GenericCANDriver extends GeneratedDriverBase<Message> {
     }
 
     @Override
-    protected Optional<String> getDefaultTransportCode() {
-        return Optional.of("socketcan");
+    protected Class<? extends Configuration> getConfigurationClass() {
+        return GenericCANConfiguration.class;
     }
 
     @Override
-    protected Class<? extends PlcConnectionConfiguration> getConfigurationClass() {
-        return GenericCANConfiguration.class;
+    public Optional<String> getDefaultTransportCode() {
+        return Optional.of("can-socketcan");
     }
 
     @Override
-    protected boolean canRead() {
-        return false;
+    public List<String> getSupportedTransportCodes() {
+        return List.of("can-socketcan", "can-virtualcan", "test");
     }
 
     @Override
@@ -79,46 +74,16 @@ public class GenericCANDriver extends GeneratedDriverBase<Message> {
         return true;
     }
 
-    /**
-     * This protocol doesn't have a disconnect procedure, so there is no need to wait for a login to finish.
-     * @return false
-     */
-    @Override
-    protected boolean awaitDisconnectComplete() {
-        return false;
-    }
-
     @Override
-    protected BaseOptimizer getOptimizer() {
-        return null;
+    protected ConnectionBase<?> getConnection(Configuration configuration,
+                                              TransportInstance<?> transportInstance,
+                                              AuditLog auditLog) {
+        return new GenericCANConnection((GenericCANConfiguration) configuration, transportInstance, auditLog);
     }
 
     @Override
-    protected ProtocolStackConfigurer<Message> getStackConfigurer() {
-        throw new PlcRuntimeException("Generic CAN driver requires access to transport layer.");
-    }
-
-    @Override
-    protected ProtocolStackConfigurer<Message> getStackConfigurer(Transport transport) {
-        if (!(transport instanceof CANTransport)) {
-            throw new PlcRuntimeException("Generic CAN Driver requires CAN Transport instance");
-        }
-
-        CANTransport<Message> canTransport = (CANTransport<Message>) transport;
-        return CustomProtocolStackConfigurer.builder(canTransport.getMessageType(), canTransport::getMessageInput)
-            .withProtocol(cfg -> {
-                GenericCANProtocolLogic protocolLogic = new GenericCANProtocolLogic();
-                ConfigurationFactory.configure(cfg, protocolLogic);
-                return new CANDriverAdapter<>(protocolLogic,
-                    canTransport.getMessageType(), canTransport.adapter(),
-                    new GenericCANFrameDataHandler(canTransport::getTransportFrameBuilder),
-                    new GenericCANTagHandler()
-                );
-            })
-            .withDriverContext(cfg -> new GenericCANDriverContext())
-            .withPacketSizeEstimator(cfg -> canTransport.getEstimator())
-            .littleEndian()
-            .build();
+    public PlcTag prepareTag(String tagAddress) {
+        return GenericCANTag.matches(tagAddress).orElse(null);
     }
 
 }
diff --git a/plc4j/drivers/can/src/main/java/org/apache/plc4x/java/can/generic/GenericCANMessageCodec.java b/plc4j/drivers/can/src/main/java/org/apache/plc4x/java/can/generic/GenericCANMessageCodec.java
new file mode 100644
index 0000000000..96881754cc
--- /dev/null
+++ b/plc4j/drivers/can/src/main/java/org/apache/plc4x/java/can/generic/GenericCANMessageCodec.java
@@ -0,0 +1,106 @@
+/*
+ * 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
+ *
+ *   https://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.generic;
+
+import org.apache.plc4x.java.can.generic.transport.GenericFrame;
+import org.apache.plc4x.java.spi.buffers.api.exceptions.BufferException;
+import org.apache.plc4x.java.spi.buffers.bytebased.ReadBufferByteBased;
+import org.apache.plc4x.java.spi.drivers.MessageCodecBase;
+import org.apache.plc4x.java.spi.drivers.exceptions.MessageCodecException;
+import org.apache.plc4x.java.spi.transports.api.TransportInstance;
+import org.apache.plc4x.java.spi.transports.api.exceptions.TransportException;
+
+import java.util.function.Consumer;
+
+/**
+ * MessageCodec for raw CAN frames as the new-SPI CAN transports
+ * ({@code can-socketcan}, {@code can-virtualcan}) serialize them onto the
+ * transport's byte stream.
+ *
+ * <p>Each frame is a fixed 16-byte block:
+ * <pre>
+ *   canId(4 BE) + dlc(1) + padding(3) + data(8, zero-padded)
+ * </pre>
+ * Standard/extended discrimination uses the top bit of canId (EFF flag, 0x80000000).</p>
+ */
+public class GenericCANMessageCodec extends MessageCodecBase<GenericFrame> {
+
+    private static final int FRAME_SIZE = 16;
+    private static final int EFF_FLAG = 0x80000000;
+    private static final int STANDARD_ID_MASK = 0x7FF;
+    private static final int EXTENDED_ID_MASK = 0x1FFFFFFF;
+
+    public GenericCANMessageCodec(TransportInstance<?> transportInstance, Consumer<GenericFrame> messageHandler) {
+        super("Generic CAN", transportInstance, messageHandler);
+    }
+
+    @Override
+    protected int getMinimumHeaderSize() {
+        return FRAME_SIZE;
+    }
+
+    @Override
+    protected int calculateTotalMessageSize(byte[] header, int availableBytes) throws MessageCodecException {
+        return FRAME_SIZE;
+    }
+
+    @Override
+    protected GenericFrame parseMessage(ReadBufferByteBased readBuffer) throws BufferException {
+        // The transport hands us frames already aligned to FRAME_SIZE; just
+        // read the bytes back out and pick apart the id + payload by hand.
+        // Using the buffer's underlying byte array avoids a second copy.
+        byte[] frame = readBuffer.getBytes();
+        int rawId = ((frame[0] & 0xFF) << 24) | ((frame[1] & 0xFF) << 16)
+            | ((frame[2] & 0xFF) << 8) | (frame[3] & 0xFF);
+        boolean extended = (rawId & EFF_FLAG) != 0;
+        int canId = rawId & (extended ? EXTENDED_ID_MASK : STANDARD_ID_MASK);
+        int dlc = frame[4] & 0xFF;
+        if (dlc < 0 || dlc > 8) {
+            dlc = 0;
+        }
+        byte[] data = new byte[dlc];
+        System.arraycopy(frame, 8, data, 0, dlc);
+        return new GenericFrame(canId, data);
+    }
+
+    /**
+     * Serializes a {@link GenericFrame} into the 16-byte transport wire format
+     * and writes it directly to the transport. Bypasses the
+     * {@link MessageCodecBase#send(org.apache.plc4x.java.spi.buffers.api.Message)}
+     * path because {@code GenericFrame} doesn't implement {@code Message}.
+     */
+    public void sendFrame(GenericFrame frame) throws MessageCodecException {
+        byte[] bytes = new byte[FRAME_SIZE];
+        int canId = frame.getNodeId();
+        bytes[0] = (byte) ((canId >> 24) & 0xFF);
+        bytes[1] = (byte) ((canId >> 16) & 0xFF);
+        bytes[2] = (byte) ((canId >> 8) & 0xFF);
+        bytes[3] = (byte) (canId & 0xFF);
+        byte[] data = frame.getData();
+        int dlc = Math.min(data.length, 8);
+        bytes[4] = (byte) dlc;
+        System.arraycopy(data, 0, bytes, 8, dlc);
+        try {
+            transportInstance.write(bytes);
+        } catch (TransportException e) {
+            throw new MessageCodecException("Failed to send CAN frame", e);
+        }
+    }
+
+}
diff --git a/plc4j/drivers/can/src/main/java/org/apache/plc4x/java/can/generic/configuration/GenericCANConfiguration.java b/plc4j/drivers/can/src/main/java/org/apache/plc4x/java/can/generic/configuration/GenericCANConfiguration.java
index 2d77af8f04..c96dde0403 100644
--- a/plc4j/drivers/can/src/main/java/org/apache/plc4x/java/can/generic/configuration/GenericCANConfiguration.java
+++ b/plc4j/drivers/can/src/main/java/org/apache/plc4x/java/can/generic/configuration/GenericCANConfiguration.java
@@ -18,12 +18,12 @@
  */
 package org.apache.plc4x.java.can.generic.configuration;
 
-import org.apache.plc4x.java.spi.configuration.PlcConnectionConfiguration;
-import org.apache.plc4x.java.spi.configuration.annotations.ConfigurationParameter;
-import org.apache.plc4x.java.spi.configuration.annotations.Description;
-import org.apache.plc4x.java.spi.configuration.annotations.defaults.IntDefaultValue;
+import org.apache.plc4x.java.spi.config.Configuration;
+import org.apache.plc4x.java.spi.config.annotations.ConfigurationParameter;
+import org.apache.plc4x.java.spi.config.annotations.Description;
+import org.apache.plc4x.java.spi.config.annotations.defaults.IntDefaultValue;
 
-public class GenericCANConfiguration implements PlcConnectionConfiguration {
+public class GenericCANConfiguration implements Configuration {
 
     @ConfigurationParameter("node-id")
     @Description("Node id of the target device.")
diff --git a/plc4j/drivers/can/src/main/java/org/apache/plc4x/java/can/generic/context/GenericCANDriverContext.java b/plc4j/drivers/can/src/main/java/org/apache/plc4x/java/can/generic/context/GenericCANDriverContext.java
deleted file mode 100644
index 0b9cf34a7e..0000000000
--- a/plc4j/drivers/can/src/main/java/org/apache/plc4x/java/can/generic/context/GenericCANDriverContext.java
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * 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
- *
- *   https://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.generic.context;
-
-import org.apache.plc4x.java.spi.context.DriverContext;
-
-public class GenericCANDriverContext implements DriverContext {
-}
diff --git a/plc4j/drivers/can/src/main/java/org/apache/plc4x/java/can/generic/protocol/GenericCANProtocolLogic.java b/plc4j/drivers/can/src/main/java/org/apache/plc4x/java/can/generic/protocol/GenericCANProtocolLogic.java
deleted file mode 100644
index f5704b3b6b..0000000000
--- a/plc4j/drivers/can/src/main/java/org/apache/plc4x/java/can/generic/protocol/GenericCANProtocolLogic.java
+++ /dev/null
@@ -1,236 +0,0 @@
-/*
- * 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
- *
- *   https://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.generic.protocol;
-
-import java.util.Map.Entry;
-
-import org.apache.plc4x.java.api.messages.*;
-import org.apache.plc4x.java.api.model.PlcConsumerRegistration;
-import org.apache.plc4x.java.api.model.PlcTag;
-import org.apache.plc4x.java.api.model.PlcSubscriptionHandle;
-import org.apache.plc4x.java.api.types.PlcResponseCode;
-import org.apache.plc4x.java.api.types.PlcSubscriptionType;
-import org.apache.plc4x.java.api.value.PlcValue;
-import org.apache.plc4x.java.can.adapter.Plc4xCANProtocolBase;
-import org.apache.plc4x.java.can.generic.tag.GenericCANTag;
-import org.apache.plc4x.java.can.generic.tag.GenericCANTagHandler;
-import org.apache.plc4x.java.can.generic.transport.GenericFrame;
-import org.apache.plc4x.java.genericcan.readwrite.DataItem;
-import org.apache.plc4x.java.spi.ConversationContext;
-import org.apache.plc4x.java.spi.connection.PlcTagHandler;
-import org.apache.plc4x.java.spi.context.DriverContext;
-import org.apache.plc4x.java.spi.generation.*;
-import org.apache.plc4x.java.spi.messages.*;
-import org.apache.plc4x.java.spi.messages.utils.DefaultPlcResponseItem;
-import org.apache.plc4x.java.spi.messages.utils.PlcResponseItem;
-import org.apache.plc4x.java.spi.model.DefaultPlcConsumerRegistration;
-import org.apache.plc4x.java.spi.model.DefaultPlcSubscriptionTag;
-import org.apache.plc4x.java.spi.transaction.RequestTransactionManager;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.time.Instant;
-import java.util.*;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.function.Consumer;
-
-public class GenericCANProtocolLogic extends Plc4xCANProtocolBase<GenericFrame> implements PlcSubscriber {
-
-    private final Logger logger = LoggerFactory.getLogger(GenericCANProtocolLogic.class);
-
-    private RequestTransactionManager tm;
-    private final Map<DefaultPlcConsumerRegistration, Consumer<PlcSubscriptionEvent>> consumers = new ConcurrentHashMap<>();
-
-    @Override
-    public void setDriverContext(DriverContext driverContext) {
-        super.setDriverContext(driverContext);
-        this.tm = new RequestTransactionManager(1);
-    }
-
-    @Override
-    public void close(ConversationContext<GenericFrame> context) {
-        tm.shutdown();
-    }
-
-    @Override
-    public void setConversationContext(ConversationContext<GenericFrame> conversationContext) {
-        super.setConversationContext(conversationContext);
-    }
-
-    @Override
-    public PlcTagHandler getTagHandler() {
-        return new GenericCANTagHandler();
-    }
-
-    @Override
-    public void onConnect(ConversationContext<GenericFrame> context) {
-        context.fireConnected();
-    }
-
-    @Override
-    public void onDisconnect(ConversationContext<GenericFrame> context) {
-        context.fireDisconnected();
-    }
-
-    @Override
-    public void decode(ConversationContext<GenericFrame> context, GenericFrame msg) throws Exception {
-        for (Map.Entry<DefaultPlcConsumerRegistration, Consumer<PlcSubscriptionEvent>> entry : consumers.entrySet()) {
-            DefaultPlcConsumerRegistration registration = entry.getKey();
-            Consumer<PlcSubscriptionEvent> consumer = entry.getValue();
-            for (PlcSubscriptionHandle handle : registration.getSubscriptionHandles()) {
-                GenericCANSubscriptionHandle subscription = (GenericCANSubscriptionHandle) handle;
-                Map<String, PlcResponseItem<PlcValue>> tags = new LinkedHashMap<>();
-                ReadBuffer buffer = new ReadBufferByteBased(msg.getData(), ByteOrder.LITTLE_ENDIAN);
-                buffer.pullContext("readTags");
-                if (subscription.matches(msg.getNodeId())) {
-                    byte[] data = msg.getData();
-                    ReadBufferByteBased readBuffer = new ReadBufferByteBased(data, ByteOrder.LITTLE_ENDIAN);
-                    for (Entry<String, GenericCANTag> tag : subscription.getTags().entrySet()) {
-                        try {
-                            PlcValue value = read(readBuffer, tag.getValue(), data.length);
-                            if (value == null) {
-                                tags.put(tag.getKey(), new DefaultPlcResponseItem<>(PlcResponseCode.INTERNAL_ERROR, null));
-                            } else {
-                                tags.put(tag.getKey(), new DefaultPlcResponseItem<>(PlcResponseCode.OK, value));
-                            }
-                        } catch (ParseException e) {
-                            tags.put(tag.getKey(), new DefaultPlcResponseItem<>(PlcResponseCode.INVALID_DATA, null));
-                        }
-                    }
-                    consumer.accept(new DefaultPlcSubscriptionEvent(Instant.now(), tags));
-                }
-                buffer.closeContext("readTags");
-            }
-        }
-    }
-
-    private PlcValue read(ReadBuffer buffer, GenericCANTag tag, int length) throws ParseException {
-        try {
-            buffer.pullContext("read-" + tag);
-            return DataItem.staticParse(buffer, tag.getDataType(), length);
-        } finally {
-            buffer.closeContext("read-" + tag);
-        }
-    }
-
-    private void write(WriteBuffer buffer, GenericCANTag tag, PlcValue value) throws SerializationException {
-        try {
-            buffer.pushContext("write-" + tag);
-            WriteBufferByteBased writeBuffer = new WriteBufferByteBased(DataItem.getLengthInBytes(value, tag.getDataType(), value.getLength()), ByteOrder.LITTLE_ENDIAN);
-            DataItem.staticSerialize(writeBuffer, value, tag.getDataType(), value.getLength(), ByteOrder.LITTLE_ENDIAN);
-            buffer.writeByteArray(writeBuffer.getBytes());
-        } finally {
-            buffer.popContext("write-" + tag);
-        }
-    }
-
-    @Override
-    public CompletableFuture<PlcWriteResponse> write(PlcWriteRequest writeRequest) {
-        final RequestTransactionManager.RequestTransaction transaction = tm.startRequest();
-
-        CompletableFuture<PlcWriteResponse> response = new CompletableFuture<>();
-        transaction.submit(() -> {
-            Map<Integer, WriteBufferByteBased> messages = new LinkedHashMap<>();
-            Map<Integer, Map<String, PlcResponseCode>> responses = new HashMap<>();
-
-            for (String tagName : writeRequest.getTagNames()) {
-                PlcTag plcTag = writeRequest.getTag(tagName);
-                if (!(plcTag instanceof GenericCANTag)) {
-                    responses.computeIfAbsent(-1, (node) -> new HashMap<>())
-                        .put(tagName, PlcResponseCode.UNSUPPORTED);
-                    continue;
-                }
-                GenericCANTag canTag = (GenericCANTag) plcTag;
-                WriteBuffer buffer = messages.computeIfAbsent(canTag.getNodeId(), (node) -> new WriteBufferByteBased(8, ByteOrder.LITTLE_ENDIAN));
-
-                Map<String, PlcResponseCode> statusMap = responses.computeIfAbsent(canTag.getNodeId(), (node) -> new HashMap<>());
-
-                PlcValue value = writeRequest.getPlcValue(tagName);
-                try {
-                    write(buffer, canTag, value);
-                    statusMap.put(tagName, PlcResponseCode.OK);
-                } catch (SerializationException e) {
-                    statusMap.put(tagName, PlcResponseCode.INVALID_DATA);
-                }
-            }
-
-            Map<String, PlcResponseCode> codes = new HashMap<>();
-            for (Map.Entry<Integer, WriteBufferByteBased> message : messages.entrySet()) {
-                boolean discarded = false;
-                for (Map.Entry<String, PlcResponseCode> entry : responses.get(message.getKey()).entrySet()) {
-                    codes.put(entry.getKey(), entry.getValue());
-                    if (!discarded && entry.getValue() != PlcResponseCode.OK) {
-                        logger.info("Discarding writing of frame with tag {}. Node {} will not be communicated.", entry.getKey(), message.getKey());
-                        discarded = true;
-                    }
-                }
-                if (!discarded) {
-                    byte[] data = message.getValue().getBytes();
-                    logger.debug("Writing message with id {} and {} bytes of data", message.getKey(), data.length);
-                    conversationContext.sendToWire(new GenericFrame(message.getKey(), data));
-                }
-            }
-
-            response.complete(new DefaultPlcWriteResponse(writeRequest, codes));
-            transaction.endRequest();
-        });
-
-        return response;
-    }
-
-    @Override
-    public CompletableFuture<PlcSubscriptionResponse> subscribe(PlcSubscriptionRequest request) {
-        DefaultPlcSubscriptionRequest rq = (DefaultPlcSubscriptionRequest) request;
-
-        Map<String, PlcResponseItem<PlcSubscriptionHandle>> answers = new LinkedHashMap<>();
-        DefaultPlcSubscriptionResponse response = new DefaultPlcSubscriptionResponse(rq, answers);
-
-        Map<Integer, GenericCANSubscriptionHandle> handles = new LinkedHashMap<>();
-        for (String key : rq.getTagNames()) {
-            DefaultPlcSubscriptionTag subscription = (DefaultPlcSubscriptionTag) rq.getTag(key);
-            if (subscription.getPlcSubscriptionType() != PlcSubscriptionType.EVENT) {
-                answers.put(key, new DefaultPlcResponseItem<>(PlcResponseCode.UNSUPPORTED, null));
-            } else if (subscription.getTag() instanceof GenericCANTag) {
-                GenericCANTag canTag = (GenericCANTag) subscription.getTag();
-                GenericCANSubscriptionHandle subscriptionHandle = handles.computeIfAbsent(canTag.getNodeId(),
-                    node -> new GenericCANSubscriptionHandle(this, node)
-                );
-                answers.put(key, new DefaultPlcResponseItem<>(PlcResponseCode.OK, subscriptionHandle));
-                subscriptionHandle.add(key, canTag);
-            } else {
-                answers.put(key, new DefaultPlcResponseItem<>(PlcResponseCode.INVALID_ADDRESS, null));
-            }
-        }
-
-        return CompletableFuture.completedFuture(response);
-    }
-
-    @Override
-    public PlcConsumerRegistration register(Consumer<PlcSubscriptionEvent> consumer, Collection<PlcSubscriptionHandle> handles) {
-        final DefaultPlcConsumerRegistration consumerRegistration = new DefaultPlcConsumerRegistration(this, consumer, handles.toArray(new PlcSubscriptionHandle[0]));
-        consumers.put(consumerRegistration, consumer);
-        return consumerRegistration;
-    }
-
-    @Override
-    public void unregister(PlcConsumerRegistration registration) {
-        consumers.remove(registration);
-    }
-}
diff --git a/plc4j/drivers/can/src/main/java/org/apache/plc4x/java/can/generic/protocol/GenericCANSubscriptionHandle.java b/plc4j/drivers/can/src/main/java/org/apache/plc4x/java/can/generic/protocol/GenericCANSubscriptionHandle.java
index 6bda302092..b60536f3ad 100644
--- a/plc4j/drivers/can/src/main/java/org/apache/plc4x/java/can/generic/protocol/GenericCANSubscriptionHandle.java
+++ b/plc4j/drivers/can/src/main/java/org/apache/plc4x/java/can/generic/protocol/GenericCANSubscriptionHandle.java
@@ -18,20 +18,25 @@
  */
 package org.apache.plc4x.java.can.generic.protocol;
 
+import org.apache.plc4x.java.api.messages.PlcSubscriptionEvent;
+import org.apache.plc4x.java.api.model.PlcConsumerRegistration;
+import org.apache.plc4x.java.api.model.PlcSubscriptionHandle;
+import org.apache.plc4x.java.can.generic.tag.GenericCANTag;
+import org.apache.plc4x.java.spi.drivers.functions.PlcSubscriber;
+
 import java.util.Collections;
 import java.util.LinkedHashMap;
 import java.util.Map;
-import org.apache.plc4x.java.can.generic.tag.GenericCANTag;
-import org.apache.plc4x.java.spi.messages.PlcSubscriber;
-import org.apache.plc4x.java.spi.model.DefaultPlcSubscriptionHandle;
+import java.util.function.Consumer;
 
-public class GenericCANSubscriptionHandle extends DefaultPlcSubscriptionHandle {
+public class GenericCANSubscriptionHandle implements PlcSubscriptionHandle {
 
+    private final PlcSubscriber subscriber;
     private final Map<String, GenericCANTag> tags = new LinkedHashMap<>();
     private final Integer nodeId;
 
     public GenericCANSubscriptionHandle(PlcSubscriber subscriber, Integer nodeId) {
-        super(subscriber);
+        this.subscriber = subscriber;
         this.nodeId = nodeId;
     }
 
@@ -39,20 +44,21 @@ public class GenericCANSubscriptionHandle extends DefaultPlcSubscriptionHandle {
         return nodeId == identifier;
     }
 
-    public String toString() {
-        return "GenericCANSubscriptionHandle [node=" + nodeId + " " + intAndHex(nodeId) + "]";
+    @Override
+    public PlcConsumerRegistration register(Consumer<PlcSubscriptionEvent> consumer) {
+        return subscriber.registerConsumer(consumer, Collections.singletonList(this));
     }
 
     public void add(String name, GenericCANTag tag) {
         tags.put(name, tag);
     }
 
-    private static String intAndHex(int val) {
-        return val + "(0x" + Integer.toHexString(val) + ")";
-    }
-
     public Map<String, GenericCANTag> getTags() {
         return Collections.unmodifiableMap(tags);
     }
-}
 
+    @Override
+    public String toString() {
+        return "GenericCANSubscriptionHandle[node=" + nodeId + " (0x" + Integer.toHexString(nodeId) + ")]";
+    }
+}
diff --git a/plc4j/drivers/can/src/main/java/org/apache/plc4x/java/can/generic/tag/GenericCANTag.java b/plc4j/drivers/can/src/main/java/org/apache/plc4x/java/can/generic/tag/GenericCANTag.java
index ea8a2d34fe..57d73356b2 100644
--- a/plc4j/drivers/can/src/main/java/org/apache/plc4x/java/can/generic/tag/GenericCANTag.java
+++ b/plc4j/drivers/can/src/main/java/org/apache/plc4x/java/can/generic/tag/GenericCANTag.java
@@ -21,6 +21,9 @@ package org.apache.plc4x.java.can.generic.tag;
 import org.apache.plc4x.java.api.exceptions.PlcRuntimeException;
 import org.apache.plc4x.java.api.model.ArrayInfo;
 import org.apache.plc4x.java.api.model.PlcTag;
+import org.apache.plc4x.java.api.types.PlcValueType;
+import org.apache.plc4x.java.genericcan.readwrite.GenericCANDataType;
+import org.apache.plc4x.java.spi.drivers.model.DefaultArrayInfo;
 
 import java.util.Collections;
 import java.util.List;
@@ -28,13 +31,10 @@ import java.util.Optional;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-import org.apache.plc4x.java.api.types.PlcValueType;
-import org.apache.plc4x.java.genericcan.readwrite.GenericCANDataType;
-import org.apache.plc4x.java.spi.model.DefaultArrayInfo;
-
 public class GenericCANTag implements PlcTag {
 
-    public static final Pattern ADDRESS_PATTERN = Pattern.compile("(?<nodeId>\\d+):(?<dataType>\\w+)(?:\\[(?<arraySize>\\d+)\\])?");
+    public static final Pattern ADDRESS_PATTERN =
+        Pattern.compile("(?<nodeId>\\d+):(?<dataType>\\w+)(?:\\[(?<arraySize>\\d+)\\])?");
     private final int nodeId;
     private final GenericCANDataType dataType;
     private final int arraySize;
@@ -52,7 +52,7 @@ public class GenericCANTag implements PlcTag {
     @Override
     public String getAddressString() {
         String address = nodeId + ":" + dataType.name();
-        if(arraySize != 1) {
+        if (arraySize != 1) {
             address += "[" + arraySize + "]";
         }
         return address;
@@ -65,7 +65,7 @@ public class GenericCANTag implements PlcTag {
 
     @Override
     public List<ArrayInfo> getArrayInfo() {
-        if(arraySize > 1) {
+        if (arraySize > 1) {
             return Collections.singletonList(new DefaultArrayInfo(0, arraySize));
         }
         return Collections.emptyList();
@@ -81,24 +81,23 @@ public class GenericCANTag implements PlcTag {
 
     public static Optional<GenericCANTag> matches(String tagQuery) {
         Matcher matcher = ADDRESS_PATTERN.matcher(tagQuery);
-        return matcher.matches() ? Optional.of(GenericCANTag.create(matcher)) : Optional.empty();
+        return matcher.matches() ? Optional.of(create(matcher)) : Optional.empty();
     }
 
     static GenericCANTag create(Matcher tagQuery) {
         int nodeId = Integer.parseInt(tagQuery.group("nodeId"));
         String type = tagQuery.group("dataType");
-
         GenericCANDataType dataType;
         try {
-             dataType = GenericCANDataType.valueOf(type);
+            dataType = GenericCANDataType.valueOf(type);
         } catch (IllegalArgumentException e) {
             throw new PlcRuntimeException("Could not create tag with data type " + type, e);
         }
         int arraySize = tagQuery.group("arraySize") != null ? Integer.parseInt(tagQuery.group("arraySize")) : 0;
-
         return new GenericCANTag(nodeId, dataType, arraySize);
     }
 
+    @Override
     public String toString() {
         return "GenericCANTag(" + nodeId + ":" + dataType.name() + (arraySize == 0 ? "" : "[" + arraySize + "]");
     }
diff --git a/plc4j/drivers/can/src/main/java/org/apache/plc4x/java/can/generic/tag/GenericCANTagHandler.java b/plc4j/drivers/can/src/main/java/org/apache/plc4x/java/can/generic/tag/GenericCANTagHandler.java
index b3fa32e9da..004324df7d 100644
--- a/plc4j/drivers/can/src/main/java/org/apache/plc4x/java/can/generic/tag/GenericCANTagHandler.java
+++ b/plc4j/drivers/can/src/main/java/org/apache/plc4x/java/can/generic/tag/GenericCANTagHandler.java
@@ -18,17 +18,17 @@
  */
 package org.apache.plc4x.java.can.generic.tag;
 
+import org.apache.plc4x.java.api.exceptions.PlcInvalidTagException;
 import org.apache.plc4x.java.api.model.PlcQuery;
-import org.apache.plc4x.java.spi.connection.PlcTagHandler;
-
-import java.util.Optional;
+import org.apache.plc4x.java.api.model.PlcTag;
+import org.apache.plc4x.java.spi.drivers.tags.PlcTagHandler;
 
 public class GenericCANTagHandler implements PlcTagHandler {
 
     @Override
-    public GenericCANTag parseTag(String tagAddress) {
-        Optional<GenericCANTag> tag = GenericCANTag.matches(tagAddress);
-        return tag.orElse(null);
+    public PlcTag parseTag(String tagAddress) {
+        return GenericCANTag.matches(tagAddress)
+            .orElseThrow(() -> new PlcInvalidTagException(tagAddress));
     }
 
     @Override
diff --git a/plc4j/drivers/can/src/main/java/org/apache/plc4x/java/can/generic/transport/GenericCANFrameDataHandler.java b/plc4j/drivers/can/src/main/java/org/apache/plc4x/java/can/generic/transport/GenericCANFrameDataHandler.java
deleted file mode 100644
index 7103ddd62a..0000000000
--- a/plc4j/drivers/can/src/main/java/org/apache/plc4x/java/can/generic/transport/GenericCANFrameDataHandler.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * 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
- *
- *   https://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.generic.transport;
-
-import java.util.function.Supplier;
-import org.apache.plc4x.java.spi.generation.*;
-import org.apache.plc4x.java.transport.can.CANFrameBuilder;
-import org.apache.plc4x.java.transport.can.CANTransport;
-import org.apache.plc4x.java.transport.can.FrameData;
-
-/**
- * Generic CAN frame handler turn a wire level message @{@link FrameData} into a wrapper which
- * does not have any specific other than node id and data.
- *
- * Because it is used by generic purpose driver it can not assume any semantics on message role.
- */
-public class GenericCANFrameDataHandler implements CANTransport.FrameHandler<Message, GenericFrame> {
-
-    private final Supplier<CANFrameBuilder<Message>> frameBuilder;
-
-    public GenericCANFrameDataHandler(Supplier<CANFrameBuilder<Message>> frameBuilder) {
-        this.frameBuilder = frameBuilder;
-    }
-
-    @Override
-    public GenericFrame fromCAN(FrameData frame) {
-        return new GenericFrame(frame.getNodeId(), frame.getData());
-    }
-
-    @Override
-    public Message toCAN(GenericFrame frame) {
-        return frameBuilder.get().withId(frame.getNodeId())
-            .withData(frame.getData())
-            .create();
-    }
-
-}
diff --git a/plc4j/drivers/can/src/main/java/org/apache/plc4x/java/can/generic/transport/GenericFrame.java b/plc4j/drivers/can/src/main/java/org/apache/plc4x/java/can/generic/transport/GenericFrame.java
index 3724f9bca1..05c262f829 100644
--- a/plc4j/drivers/can/src/main/java/org/apache/plc4x/java/can/generic/transport/GenericFrame.java
+++ b/plc4j/drivers/can/src/main/java/org/apache/plc4x/java/can/generic/transport/GenericFrame.java
@@ -18,10 +18,20 @@
  */
 package org.apache.plc4x.java.can.generic.transport;
 
+import org.apache.plc4x.java.spi.buffers.api.Message;
+import org.apache.plc4x.java.spi.buffers.api.WriteBuffer;
+import org.apache.plc4x.java.spi.buffers.api.exceptions.BufferException;
+
 /**
- * Wrapper for wire level data.
+ * Wrapper for one CAN frame's address + payload.
+ *
+ * <p>Implements {@link Message} so it fits the {@code MessageCodecBase<M extends Message>}
+ * generic; serialization is the same 16-byte wire layout the SocketCAN / VirtualCAN
+ * transports already speak: canId(4 BE) + dlc(1) + padding(3) + data(8).</p>
  */
-public class GenericFrame {
+public class GenericFrame implements Message {
+
+    private static final int FRAME_SIZE = 16;
 
     private final int nodeId;
     private final byte[] data;
@@ -39,4 +49,27 @@ public class GenericFrame {
         return data;
     }
 
+    @Override
+    public int getLengthInBytes() {
+        return FRAME_SIZE;
+    }
+
+    @Override
+    public int getLengthInBits() {
+        return FRAME_SIZE * 8;
+    }
+
+    @Override
+    public void serialize(WriteBuffer writeBuffer) throws BufferException {
+        writeBuffer.writeUnsignedInt(32, nodeId);
+        int dlc = Math.min(data.length, 8);
+        writeBuffer.writeUnsignedInt(8, dlc);
+        writeBuffer.writeUnsignedInt(8, 0);
+        writeBuffer.writeUnsignedInt(8, 0);
+        writeBuffer.writeUnsignedInt(8, 0);
+        for (int i = 0; i < 8; i++) {
+            writeBuffer.writeUnsignedInt(8, i < dlc ? (data[i] & 0xFF) : 0);
+        }
+    }
+
 }
diff --git a/plc4j/drivers/can/src/test/java/org/apache/plc4x/java/can/generic/GenericCANDriverTest.java b/plc4j/drivers/can/src/test/java/org/apache/plc4x/java/can/generic/GenericCANDriverTest.java
deleted file mode 100644
index 7abd581efe..0000000000
--- a/plc4j/drivers/can/src/test/java/org/apache/plc4x/java/can/generic/GenericCANDriverTest.java
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * 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
- *
- *   https://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.generic;
-
-import io.netty.buffer.ByteBuf;
-import io.netty.channel.Channel;
-import io.netty.channel.embedded.Plc4xEmbeddedChannel;
-import java.lang.reflect.Array;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.atomic.AtomicReference;
-
-import org.apache.plc4x.java.DefaultPlcDriverManager;
-import org.apache.plc4x.java.api.PlcConnection;
-import org.apache.plc4x.java.api.exceptions.PlcConnectionException;
-import org.apache.plc4x.java.api.messages.PlcSubscriptionEvent;
-import org.apache.plc4x.java.api.messages.PlcSubscriptionRequest;
-import org.apache.plc4x.java.api.messages.PlcSubscriptionRequest.Builder;
-import org.apache.plc4x.java.api.messages.PlcWriteRequest;
-import org.apache.plc4x.java.spi.connection.ChannelExposingConnection;
-import org.junit.jupiter.api.Disabled;
-import org.junit.jupiter.api.Test;
-
-import static java.util.Map.entry;
-import static org.junit.jupiter.api.Assertions.*;
-
-/**
- * Test of generic can driver with virtual can transport.
- *
- * This test have additional role of confirming end to end behavior of driver and transport layer.
- * The virtual can transport rely on netty channel/pipeline infrastructure, hence it does not have
- * any backend such as memory queue. It simply converts message to a stream and expect it to be read.
- */
-public class GenericCANDriverTest {
-
-    @Test
-    void testConnection() throws PlcConnectionException {
-        PlcConnection connection = new DefaultPlcDriverManager().getConnection("genericcan:virtualcan://");
-
-        assertNotNull(connection);
-        assertTrue(connection.isConnected());
-        assertFalse(connection.getMetadata().isReadSupported());
-        assertTrue(connection.getMetadata().isWriteSupported());
-        assertTrue(connection.getMetadata().isSubscribeSupported());
-    }
-
-    @Test
-    void testSubscribeAndWrite() throws Exception {
-        Map<String, Entry<String, Object>> tagMap = Map.ofEntries(
-            entry("tag1", entry("200:BYTE", (short) 0x0A)),
-            entry("tag2", entry("200:INTEGER8", (byte) 20)),
-            entry("tag3", entry("200:INTEGER8", (byte) 30))
-        );
-
-        subscribeAndWrite(tagMap);
-    }
-
-    @Test
-    void testSubscribeAndWriteRawArray() throws Exception {
-        Map<String, Entry<String, Object>> tagMap = Map.ofEntries(
-            entry("arr1", entry("201:RAW", new byte[] {
-                (short) 0, (short) 1, (short) 2, (short) 3, (short) 4, (short) 5, (short) 6, (short) 7}
-            ))
-        );
-
-        subscribeAndWrite(tagMap);
-    }
-
-    @Test
-    @Disabled("Writing arrays requires use of RAW type")
-    void testSubscribeAndWriteByteArray() throws Exception {
-        Map<String, Entry<String, Object>> tagMap = Map.ofEntries(
-            entry("arr1", entry("201:BYTE[8]", new byte[] {
-                (short) 0, (short) 1, (short) 2, (short) 3, (short) 4, (short) 5, (short) 6, (short) 7}
-            ))
-        );
-
-        subscribeAndWrite(tagMap);
-    }
-
-
-    void subscribeAndWrite(Map<String, Entry<String, Object>> entries) throws Exception {
-        PlcConnection connection = new DefaultPlcDriverManager().getConnection("genericcan:virtualcan://");
-
-        Plc4xEmbeddedChannel subscribeChannel = null;
-        Plc4xEmbeddedChannel writeChannel = null;
-        if (connection instanceof ChannelExposingConnection) {
-            Channel channel = ((ChannelExposingConnection) connection).getChannel();
-            if (channel instanceof Plc4xEmbeddedChannel) {
-                subscribeChannel = (Plc4xEmbeddedChannel) channel;
-                writeChannel = (Plc4xEmbeddedChannel) channel;
-            }
-        }
-        if (subscribeChannel == null) {
-            throw new IllegalArgumentException("Invalid configuration");
-        }
-
-        CountDownLatch latch = new CountDownLatch(1);
-
-        final AtomicReference<PlcSubscriptionEvent> plcEvent = new AtomicReference<>();
-        Builder subscriptionRequestBuilder = connection.subscriptionRequestBuilder();
-        entries.forEach((k, v) -> {
-            subscriptionRequestBuilder.addEventTagAddress(k, v.getKey());
-        });
-
-        PlcSubscriptionRequest subscriptionRequest = subscriptionRequestBuilder.build();
-        subscriptionRequest.execute().whenComplete((reply, error) -> {
-            if (error != null) {
-                fail(error);
-                return;
-            }
-
-            reply.getSubscriptionHandle(entries.keySet().iterator().next()).register(event -> {
-                plcEvent.set(event);
-                latch.countDown();
-            });
-        });
-
-        PlcWriteRequest.Builder writeRequestBuilder = connection.writeRequestBuilder();
-        entries.forEach((k, v) -> {
-            writeRequestBuilder.addTagAddress(k, v.getKey(), v.getValue());
-        });
-        PlcWriteRequest writeRequest = writeRequestBuilder.build();
-        writeRequest.execute().whenComplete((reply, error) -> {
-            if (error != null) {
-                fail(error);
-            }
-        }).get();
-
-        // copy outbound message to inbound queue to confirm that transport API works and subscription
-        // is properly matched against incoming message
-        ByteBuf outgoing = writeChannel.flushOutbound().readOutbound();
-        subscribeChannel.writeInbound(outgoing);
-        subscribeChannel.flushInbound();
-
-        latch.await();
-
-        PlcSubscriptionEvent event = plcEvent.get();
-        entries.forEach((k, v) -> {
-            Object object = event.getObject(k);
-            if (!v.getValue().getClass().isArray()) {
-                assertEquals(v.getValue(), object);
-                return;
-            }
-
-            // comparing arrays is a bit of nightmare due to primitives
-            int length = Array.getLength(v.getValue());
-            int readLength = Array.getLength(object);
-            if (readLength != length) {
-                throw new IllegalArgumentException("Return value length do not match reference value");
-            }
-            for (int index = 0; index < length; index++) {
-                assertEquals(Array.get(v.getValue(), index), Array.get(object, index));
-            }
-        });
-
-    }
-}
diff --git a/plc4j/drivers/can/src/test/java/org/apache/plc4x/java/can/generic/tag/GenericCANTagHandlerTest.java b/plc4j/drivers/can/src/test/java/org/apache/plc4x/java/can/generic/tag/GenericCANTagHandlerTest.java
index dbe7c350ec..ab6dfeab16 100644
--- a/plc4j/drivers/can/src/test/java/org/apache/plc4x/java/can/generic/tag/GenericCANTagHandlerTest.java
+++ b/plc4j/drivers/can/src/test/java/org/apache/plc4x/java/can/generic/tag/GenericCANTagHandlerTest.java
@@ -27,7 +27,7 @@ class GenericCANTagHandlerTest {
 
     @Test
     void checkTagSyntax() {
-        GenericCANTag tag = new GenericCANTagHandler().parseTag("200:BYTE[8]");
+        GenericCANTag tag = (GenericCANTag) new GenericCANTagHandler().parseTag("200:BYTE[8]");
 
         assertEquals(200, tag.getNodeId());
         assertEquals(PlcValueType.BYTE, tag.getPlcValueType());
diff --git a/plc4j/drivers/canopen/pom.xml b/plc4j/drivers/canopen/pom.xml
index 0e377aea24..d18923392e 100644
--- a/plc4j/drivers/canopen/pom.xml
+++ b/plc4j/drivers/canopen/pom.xml
@@ -37,9 +37,6 @@
   </properties>
 
   <build>
-    <!-- Override the path of the sources roots until the code is ported  -->
-    <sourceDirectory>src/main/temp</sourceDirectory>
-    <testSourceDirectory>src/test/temp</testSourceDirectory>
     <plugins>
       <plugin>
         <groupId>org.apache.karaf.tooling</groupId>
@@ -119,11 +116,36 @@
       <artifactId>plc4j-spi-buffers-byte</artifactId>
       <version>0.14.0-SNAPSHOT</version>
     </dependency>
+    <dependency>
+      <groupId>org.apache.plc4x</groupId>
+      <artifactId>plc4j-spi-config</artifactId>
+      <version>0.14.0-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.plc4x</groupId>
+      <artifactId>plc4j-spi-drivers</artifactId>
+      <version>0.14.0-SNAPSHOT</version>
+    </dependency>
     <dependency>
       <groupId>org.apache.plc4x</groupId>
       <artifactId>plc4j-spi-fields</artifactId>
       <version>0.14.0-SNAPSHOT</version>
     </dependency>
+    <dependency>
+      <groupId>org.apache.plc4x</groupId>
+      <artifactId>plc4j-transports-api</artifactId>
+      <version>0.14.0-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.plc4x</groupId>
+      <artifactId>plc4j-transports-can</artifactId>
+      <version>0.14.0-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.plc4x</groupId>
+      <artifactId>plc4j-utils-audit-log-api</artifactId>
+      <version>0.14.0-SNAPSHOT</version>
+    </dependency>
     <dependency>
       <groupId>org.apache.plc4x</groupId>
       <artifactId>plc4j-spi-utils</artifactId>
diff --git a/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/CANOpenConnection.java b/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/CANOpenConnection.java
new file mode 100644
index 0000000000..700c285403
--- /dev/null
+++ b/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/CANOpenConnection.java
@@ -0,0 +1,509 @@
+/*
+ * 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
+ *
+ *   https://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.canopen;
+
+import org.apache.plc4x.java.api.exceptions.PlcConnectionException;
+import org.apache.plc4x.java.api.messages.PlcReadRequest;
+import org.apache.plc4x.java.api.messages.PlcReadResponse;
+import org.apache.plc4x.java.api.messages.PlcSubscriptionEvent;
+import org.apache.plc4x.java.api.messages.PlcSubscriptionRequest;
+import org.apache.plc4x.java.api.messages.PlcSubscriptionResponse;
+import org.apache.plc4x.java.api.messages.PlcUnsubscriptionRequest;
+import org.apache.plc4x.java.api.messages.PlcUnsubscriptionResponse;
+import org.apache.plc4x.java.api.messages.PlcWriteRequest;
+import org.apache.plc4x.java.api.messages.PlcWriteResponse;
+import org.apache.plc4x.java.api.model.PlcConsumerRegistration;
+import org.apache.plc4x.java.api.model.PlcSubscriptionHandle;
+import org.apache.plc4x.java.api.model.PlcTag;
+import org.apache.plc4x.java.api.types.PlcResponseCode;
+import org.apache.plc4x.java.api.types.PlcSubscriptionType;
+import org.apache.plc4x.java.api.value.PlcValue;
+import org.apache.plc4x.java.canopen.configuration.CANOpenConfiguration;
+import org.apache.plc4x.java.canopen.conversation.CANConversation;
+import org.apache.plc4x.java.canopen.conversation.SDODownloadConversation;
+import org.apache.plc4x.java.canopen.conversation.SDOUploadConversation;
+import org.apache.plc4x.java.canopen.readwrite.CANOpenFrame;
+import org.apache.plc4x.java.canopen.readwrite.CANOpenHeartbeatPayload;
+import org.apache.plc4x.java.canopen.readwrite.CANOpenNetworkPayload;
+import org.apache.plc4x.java.canopen.readwrite.CANOpenPDO;
+import org.apache.plc4x.java.canopen.readwrite.CANOpenPDOPayload;
+import org.apache.plc4x.java.canopen.readwrite.CANOpenPayload;
+import org.apache.plc4x.java.canopen.readwrite.CANOpenService;
+import org.apache.plc4x.java.canopen.readwrite.DataItem;
+import org.apache.plc4x.java.canopen.readwrite.IndexAddress;
+import org.apache.plc4x.java.canopen.readwrite.NMTState;
+import org.apache.plc4x.java.canopen.readwrite.NMTStateRequest;
+import org.apache.plc4x.java.canopen.tag.CANOpenHeartbeatTag;
+import org.apache.plc4x.java.canopen.tag.CANOpenNMTTag;
+import org.apache.plc4x.java.canopen.tag.CANOpenPDOTag;
+import org.apache.plc4x.java.canopen.tag.CANOpenSDOTag;
+import org.apache.plc4x.java.canopen.tag.CANOpenTag;
+import org.apache.plc4x.java.canopen.tag.CANOpenTagHandler;
+import org.apache.plc4x.java.spi.buffers.api.WithOption;
+import org.apache.plc4x.java.spi.buffers.api.exceptions.BufferException;
+import org.apache.plc4x.java.spi.buffers.bytebased.WithByteBasedOption;
+import org.apache.plc4x.java.spi.buffers.bytebased.WriteBufferByteBased;
+import org.apache.plc4x.java.spi.drivers.ConnectionBase;
+import org.apache.plc4x.java.spi.drivers.exceptions.MessageCodecException;
+import org.apache.plc4x.java.spi.drivers.messages.DefaultPlcConsumerRegistration;
+import org.apache.plc4x.java.spi.drivers.messages.DefaultPlcReadResponse;
+import org.apache.plc4x.java.spi.drivers.messages.DefaultPlcSubscriptionEvent;
+import org.apache.plc4x.java.spi.drivers.messages.DefaultPlcSubscriptionResponse;
+import org.apache.plc4x.java.spi.drivers.messages.DefaultPlcSubscriptionTag;
+import org.apache.plc4x.java.spi.drivers.messages.DefaultPlcUnsubscriptionResponse;
+import org.apache.plc4x.java.spi.drivers.messages.DefaultPlcWriteResponse;
+import org.apache.plc4x.java.spi.drivers.messages.items.DefaultPlcResponseItem;
+import org.apache.plc4x.java.spi.drivers.messages.items.PlcResponseItem;
+import org.apache.plc4x.java.spi.drivers.tags.PlcTagHandler;
+import org.apache.plc4x.java.spi.transports.api.TransportInstance;
+import org.apache.plc4x.java.spi.values.DefaultPlcValueHandler;
+import org.apache.plc4x.java.spi.values.PlcList;
+import org.apache.plc4x.java.spi.values.PlcNull;
+import org.apache.plc4x.java.spi.values.PlcStruct;
+import org.apache.plc4x.java.spi.values.PlcUSINT;
+import org.apache.plc4x.java.spi.values.PlcValueHandler;
+import org.apache.plc4x.java.utils.auditlog.api.AuditLog;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.function.Consumer;
+import java.util.function.Predicate;
+
+public class CANOpenConnection extends ConnectionBase<CANOpenConfiguration> implements CANConversation {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(CANOpenConnection.class);
+
+    public static final WithOption[] LITTLE_ENDIAN_OPTIONS = {
+        WithByteBasedOption.WithByteOrder("LITTLE_ENDIAN"),
+        WithOption.WithUnsignedIntegerEncoding("unsigned-binary"),
+        WithOption.WithSignedIntegerEncoding("twos-complement"),
+        WithOption.WithFloatEncoding("IEEE754"),
+        WithOption.WithStringEncoding("UTF8")
+    };
+
+    private CANOpenMessageCodec messageCodec;
+    private volatile boolean connected = false;
+
+    private final Map<DefaultPlcConsumerRegistration, Consumer<PlcSubscriptionEvent>> consumers = new ConcurrentHashMap<>();
+
+    /**
+     * One-shot frame listeners registered by {@link #expect(Predicate, Duration)}.
+     * Each is "the next frame that matches this predicate". The dispatcher walks
+     * the list and gives the first match to one listener; non-matching listeners
+     * stay registered for the next frame.
+     */
+    private final CopyOnWriteArrayList<FrameListener> listeners = new CopyOnWriteArrayList<>();
+
+    private final ScheduledExecutorService scheduler =
+        Executors.newSingleThreadScheduledExecutor(r -> {
+            Thread t = new Thread(r, "canopen-scheduler");
+            t.setDaemon(true);
+            return t;
+        });
+    private ScheduledFuture<?> heartbeatTask;
+
+    public CANOpenConnection(CANOpenConfiguration configuration,
+                             TransportInstance<?> transportInstance,
+                             AuditLog auditLog) {
+        super(configuration, transportInstance, auditLog);
+    }
+
+    @Override
+    protected PlcTagHandler getTagHandler() {
+        return new CANOpenTagHandler();
+    }
+
+    @Override
+    protected PlcValueHandler getValueHandler() {
+        // Allow {@code PlcList} values to be passed through unwrapped, matching
+        // the old driver's behavior for array writes.
+        return new DefaultPlcValueHandler() {
+            @Override
+            public PlcValue newPlcValue(PlcTag tag, Object[] values) {
+                if (values.length > 0 && values[0] instanceof PlcList) {
+                    return (PlcList) values[0];
+                }
+                return super.newPlcValue(tag, values);
+            }
+        };
+    }
+
+    @Override
+    public boolean isConnected() {
+        return connected && messageCodec != null && messageCodec.isOpen();
+    }
+
+    @Override
+    protected void onConnect() throws PlcConnectionException {
+        messageCodec = new CANOpenMessageCodec(transportInstance, this::handleIncomingFrame);
+        startReceiving(() -> {
+            try {
+                messageCodec.processIncomingData();
+            } catch (MessageCodecException e) {
+                LOGGER.error("Error processing incoming CANopen data", e);
+            }
+        });
+        connected = true;
+        // CANopen heartbeat producer: announce BOOTED_UP once, then OPERATIONAL every 10s.
+        if (configuration.isHeartbeat()) {
+            try {
+                messageCodec.sendFrame(heartbeatFrame(NMTState.BOOTED_UP));
+            } catch (MessageCodecException e) {
+                throw new PlcConnectionException("Failed to send initial CANopen heartbeat", e);
+            }
+            heartbeatTask = scheduler.scheduleAtFixedRate(() -> {
+                try {
+                    messageCodec.sendFrame(heartbeatFrame(NMTState.OPERATIONAL));
+                } catch (MessageCodecException e) {
+                    LOGGER.warn("Failed to send CANopen heartbeat", e);
+                }
+            }, 10, 10, TimeUnit.SECONDS);
+        }
+    }
+
+    @Override
+    public void close() throws Exception {
+        connected = false;
+        if (heartbeatTask != null) {
+            heartbeatTask.cancel(false);
+            heartbeatTask = null;
+        }
+        scheduler.shutdownNow();
+        stopReceiving();
+        if (messageCodec != null) {
+            messageCodec.close();
+        }
+        consumers.clear();
+        listeners.clear();
+        super.close();
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    // Conversation (used by SDOUploadConversation / SDODownloadConversation)
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+    @Override
+    public void sendToWire(CANOpenFrame frame) {
+        try {
+            messageCodec.sendFrame(frame);
+        } catch (MessageCodecException e) {
+            throw new RuntimeException("Failed to send CANopen frame", e);
+        }
+    }
+
+    @Override
+    public CompletableFuture<CANOpenFrame> expect(Predicate<CANOpenFrame> predicate, Duration timeout) {
+        CompletableFuture<CANOpenFrame> future = new CompletableFuture<>();
+        FrameListener listener = new FrameListener(predicate, future);
+        listeners.add(listener);
+        ScheduledFuture<?> timeoutHandle = scheduler.schedule(() -> {
+            if (listeners.remove(listener)) {
+                future.completeExceptionally(new TimeoutException("Timed out waiting for matching CANopen frame"));
+            }
+        }, timeout.toMillis(), TimeUnit.MILLISECONDS);
+        future.whenComplete((f, t) -> {
+            timeoutHandle.cancel(false);
+            listeners.remove(listener);
+        });
+        return future;
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    // Incoming dispatch
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+    private void handleIncomingFrame(CANOpenFrame frame) {
+        // 1. Conversation listeners (SDO request/response) get first dibs.
+        for (FrameListener listener : listeners) {
+            if (listener.predicate.test(frame)) {
+                if (listener.future.complete(frame)) {
+                    return;
+                }
+            }
+        }
+        // 2. Frames we sent ourselves never need to be dispatched (would echo back from VirtualCAN-style transports).
+        if (frame.getNodeId() == configuration.getNodeId()) {
+            return;
+        }
+        // 3. Subscription dispatch.
+        CANOpenService service = frame.getService();
+        if (service == null) {
+            return;
+        }
+        CANOpenPayload payload = frame.getPayload();
+        if (service.getPdo() && payload instanceof CANOpenPDOPayload) {
+            publishEvent(service, frame.getNodeId(), payload);
+        } else if (service == CANOpenService.HEARTBEAT && payload instanceof CANOpenHeartbeatPayload) {
+            publishEvent(service, frame.getNodeId(), payload);
+        } else if (service == CANOpenService.NMT && payload instanceof CANOpenNetworkPayload) {
+            publishEvent(service, frame.getNodeId(), payload);
+        } else {
+            LOGGER.debug("Decoded CANopen {} from {}, message {}", service, frame.getNodeId(), payload);
+        }
+    }
+
+    private void publishEvent(CANOpenService service, int nodeId, CANOpenPayload payload) {
+        for (Entry<DefaultPlcConsumerRegistration, Consumer<PlcSubscriptionEvent>> entry : consumers.entrySet()) {
+            DefaultPlcConsumerRegistration registration = entry.getKey();
+            Consumer<PlcSubscriptionEvent> consumer = entry.getValue();
+            for (PlcSubscriptionHandle subscriptionHandle : registration.getSubscriptionHandles()) {
+                if (!(subscriptionHandle instanceof CANOpenSubscriptionHandle handle)) {
+                    continue;
+                }
+                if (!handle.matches(service, nodeId)) {
+                    continue;
+                }
+                PlcSubscriptionEvent event = buildEvent(handle, nodeId, payload);
+                if (event != null) {
+                    consumer.accept(event);
+                }
+            }
+        }
+    }
+
+    private PlcSubscriptionEvent buildEvent(CANOpenSubscriptionHandle handle, int nodeId, CANOpenPayload payload) {
+        if (payload instanceof CANOpenPDOPayload pdoPayload) {
+            CANOpenPDOTag tag = (CANOpenPDOTag) handle.getTag();
+            byte[] data = pdoPayload.getPdo().getData();
+            try {
+                PlcValue value = DataItem.staticParse(
+                    new org.apache.plc4x.java.spi.buffers.bytebased.ReadBufferByteBased(data, LITTLE_ENDIAN_OPTIONS),
+                    tag.getCanOpenDataType(), data.length);
+                return new DefaultPlcSubscriptionEvent(Instant.now(), Collections.singletonMap(
+                    handle.getName(), new DefaultPlcResponseItem<>(PlcResponseCode.OK, value)));
+            } catch (BufferException e) {
+                LOGGER.warn("Could not parse PDO payload as {}: {}", tag.getCanOpenDataType(), e.getMessage());
+                return new DefaultPlcSubscriptionEvent(Instant.now(), Collections.singletonMap(
+                    handle.getName(), new DefaultPlcResponseItem<>(PlcResponseCode.INVALID_DATA, new PlcNull())));
+            }
+        }
+        if (payload instanceof CANOpenHeartbeatPayload hb) {
+            NMTState state = hb.getState();
+            Map<String, PlcValue> fields = new LinkedHashMap<>();
+            fields.put("state", new PlcUSINT(state.getValue()));
+            fields.put("node", new PlcUSINT(nodeId));
+            return new DefaultPlcSubscriptionEvent(Instant.now(), Collections.singletonMap(
+                handle.getName(), new DefaultPlcResponseItem<>(PlcResponseCode.OK, new PlcStruct(fields))));
+        }
+        if (payload instanceof CANOpenNetworkPayload net) {
+            NMTStateRequest req = net.getRequest();
+            Map<String, PlcValue> fields = new LinkedHashMap<>();
+            fields.put("state", new PlcUSINT(req.getValue()));
+            fields.put("node", new PlcUSINT(nodeId));
+            return new DefaultPlcSubscriptionEvent(Instant.now(), Collections.singletonMap(
+                handle.getName(), new DefaultPlcResponseItem<>(PlcResponseCode.OK, new PlcStruct(fields))));
+        }
+        return null;
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    // Read (SDO upload)
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+    @Override
+    protected CompletableFuture<PlcReadResponse> onRead(PlcReadRequest readRequest) {
+        if (readRequest.getTagNames().size() != 1) {
+            CompletableFuture<PlcReadResponse> failed = new CompletableFuture<>();
+            failed.completeExceptionally(new IllegalArgumentException("SDO requires single tag to be read"));
+            return failed;
+        }
+        String tagName = readRequest.getTagNames().iterator().next();
+        PlcTag tag = readRequest.getTags().get(0);
+        if (!(tag instanceof CANOpenSDOTag sdoTag)) {
+            CompletableFuture<PlcReadResponse> failed = new CompletableFuture<>();
+            failed.completeExceptionally(new IllegalArgumentException("Only CANOpenSDOTag instances are supported"));
+            return failed;
+        }
+
+        Duration timeout = Duration.ofMillis(Math.max(1, configuration.getRequestTimeout()));
+        CompletableFuture<PlcValue> valueFuture = new CompletableFuture<>();
+        SDOUploadConversation upload = new SDOUploadConversation(this,
+            sdoTag.getNodeId(),
+            sdoTag.getAnswerNodeId() == 0 ? sdoTag.getNodeId() : sdoTag.getAnswerNodeId(),
+            new IndexAddress((int) sdoTag.getIndex(), sdoTag.getSubIndex()),
+            sdoTag.getCanOpenDataType(),
+            timeout);
+        upload.execute(valueFuture);
+
+        return valueFuture.handle((value, error) -> {
+            Map<String, PlcResponseItem<PlcValue>> tags = new HashMap<>();
+            if (error != null) {
+                tags.put(tagName, new DefaultPlcResponseItem<>(PlcResponseCode.REMOTE_ERROR, new PlcNull()));
+            } else {
+                tags.put(tagName, new DefaultPlcResponseItem<>(PlcResponseCode.OK, value));
+            }
+            return new DefaultPlcReadResponse(readRequest, tags);
+        });
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    // Write (SDO download or PDO push)
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+    @Override
+    protected CompletableFuture<PlcWriteResponse> onWrite(PlcWriteRequest writeRequest) {
+        if (writeRequest.getTagNames().size() != 1) {
+            CompletableFuture<PlcWriteResponse> failed = new CompletableFuture<>();
+            failed.completeExceptionally(new IllegalArgumentException("You can write only one tag at the time"));
+            return failed;
+        }
+
+        String tagName = writeRequest.getTagNames().iterator().next();
+        PlcTag tag = writeRequest.getTags().get(0);
+        if (!(tag instanceof CANOpenTag)) {
+            CompletableFuture<PlcWriteResponse> failed = new CompletableFuture<>();
+            failed.completeExceptionally(new IllegalArgumentException("Only CANOpenTag instances are supported"));
+            return failed;
+        }
+
+        if (tag instanceof CANOpenSDOTag sdoTag) {
+            return writeSdo(writeRequest, tagName, sdoTag);
+        }
+        if (tag instanceof CANOpenPDOTag pdoTag) {
+            return writePdo(writeRequest, tagName, pdoTag);
+        }
+
+        CompletableFuture<PlcWriteResponse> failed = new CompletableFuture<>();
+        failed.completeExceptionally(new IllegalArgumentException(
+            "Only CANOpenSDOTag / CANOpenPDOTag instances are writable"));
+        return failed;
+    }
+
+    private CompletableFuture<PlcWriteResponse> writeSdo(PlcWriteRequest writeRequest, String tagName, CANOpenSDOTag tag) {
+        PlcValue value = writeRequest.getPlcValue(tagName);
+        Duration timeout = Duration.ofMillis(Math.max(1, configuration.getRequestTimeout()));
+        CompletableFuture<PlcResponseCode> codeFuture = new CompletableFuture<>();
+        SDODownloadConversation download = new SDODownloadConversation(this,
+            tag.getNodeId(),
+            tag.getAnswerNodeId() == 0 ? tag.getNodeId() : tag.getAnswerNodeId(),
+            new IndexAddress((int) tag.getIndex(), tag.getSubIndex()),
+            value, tag.getCanOpenDataType(),
+            timeout);
+        download.execute(codeFuture);
+
+        return codeFuture.handle((code, error) -> {
+            PlcResponseCode result;
+            if (error != null) {
+                result = (error.getCause() instanceof org.apache.plc4x.java.canopen.transport.CANOpenAbortException
+                    || error instanceof org.apache.plc4x.java.canopen.transport.CANOpenAbortException)
+                    ? PlcResponseCode.REMOTE_ERROR : PlcResponseCode.INTERNAL_ERROR;
+            } else {
+                result = code;
+            }
+            return new DefaultPlcWriteResponse(writeRequest, Collections.singletonMap(tagName, result));
+        });
+    }
+
+    private CompletableFuture<PlcWriteResponse> writePdo(PlcWriteRequest writeRequest, String tagName, CANOpenPDOTag tag) {
+        PlcValue value = writeRequest.getPlcValue(tagName);
+        try {
+            int byteLen = DataItem.getLengthInBytes(value, tag.getCanOpenDataType(), value.getLength());
+            WriteBufferByteBased buffer = new WriteBufferByteBased(new byte[byteLen], LITTLE_ENDIAN_OPTIONS);
+            DataItem.staticSerialize(buffer, value, tag.getCanOpenDataType(), value.getLength());
+            CANOpenPDOPayload payload = new CANOpenPDOPayload(new CANOpenPDO(buffer.getBytes()));
+            messageCodec.sendFrame(new CANOpenFrame((short) tag.getNodeId(), tag.getService(), payload));
+            return CompletableFuture.completedFuture(new DefaultPlcWriteResponse(writeRequest,
+                Collections.singletonMap(tagName, PlcResponseCode.OK)));
+        } catch (BufferException | MessageCodecException e) {
+            CompletableFuture<PlcWriteResponse> failed = new CompletableFuture<>();
+            failed.completeExceptionally(e);
+            return failed;
+        }
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    // Subscribe / consumer registry
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+    @Override
+    protected CompletableFuture<PlcSubscriptionResponse> onSubscribe(PlcSubscriptionRequest request) {
+        Map<String, PlcResponseItem<PlcSubscriptionHandle>> answers = new LinkedHashMap<>();
+        for (String key : request.getTagNames()) {
+            DefaultPlcSubscriptionTag subscription = (DefaultPlcSubscriptionTag) request.getTag(key);
+            if (subscription.getPlcSubscriptionType() != PlcSubscriptionType.EVENT) {
+                answers.put(key, new DefaultPlcResponseItem<>(PlcResponseCode.UNSUPPORTED, null));
+            } else if (subscription.getTag() instanceof CANOpenPDOTag pdoTag) {
+                answers.put(key, new DefaultPlcResponseItem<>(PlcResponseCode.OK,
+                    new CANOpenSubscriptionHandle(this, key, pdoTag)));
+            } else if (subscription.getTag() instanceof CANOpenNMTTag nmtTag) {
+                answers.put(key, new DefaultPlcResponseItem<>(PlcResponseCode.OK,
+                    new CANOpenSubscriptionHandle(this, key, nmtTag)));
+            } else if (subscription.getTag() instanceof CANOpenHeartbeatTag hbTag) {
+                answers.put(key, new DefaultPlcResponseItem<>(PlcResponseCode.OK,
+                    new CANOpenSubscriptionHandle(this, key, hbTag)));
+            } else {
+                answers.put(key, new DefaultPlcResponseItem<>(PlcResponseCode.INVALID_ADDRESS, null));
+            }
+        }
+        return CompletableFuture.completedFuture(new DefaultPlcSubscriptionResponse(request, answers));
+    }
+
+    @Override
+    protected CompletableFuture<PlcUnsubscriptionResponse> onUnsubscribe(PlcUnsubscriptionRequest request) {
+        // Consumer registrations are torn down via onUnregisterConsumer; nothing
+        // to do on the bus side because CAN is push-only.
+        return CompletableFuture.completedFuture(new DefaultPlcUnsubscriptionResponse(request));
+    }
+
+    @Override
+    protected PlcConsumerRegistration onRegisterConsumer(Consumer<PlcSubscriptionEvent> consumer,
+                                                        Collection<PlcSubscriptionHandle> handles) {
+        DefaultPlcConsumerRegistration registration = new DefaultPlcConsumerRegistration(this, consumer,
+            handles.toArray(new PlcSubscriptionHandle[0]));
+        consumers.put(registration, consumer);
+        return registration;
+    }
+
+    @Override
+    protected void onUnregisterConsumer(PlcConsumerRegistration registration) {
+        if (registration instanceof DefaultPlcConsumerRegistration r) {
+            consumers.remove(r);
+        }
+    }
+
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+    // Helpers
+    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+
+    private CANOpenFrame heartbeatFrame(NMTState state) {
+        return new CANOpenFrame((short) configuration.getNodeId(),
+            CANOpenService.HEARTBEAT, new CANOpenHeartbeatPayload(state));
+    }
+
+    private record FrameListener(Predicate<CANOpenFrame> predicate, CompletableFuture<CANOpenFrame> future) {
+    }
+
+}
diff --git a/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/CANOpenDriver.java b/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/CANOpenDriver.java
new file mode 100644
index 0000000000..cef3b51cd2
--- /dev/null
+++ b/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/CANOpenDriver.java
@@ -0,0 +1,87 @@
+/*
+ * 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
+ *
+ *   https://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.canopen;
+
+import org.apache.plc4x.java.api.model.PlcTag;
+import org.apache.plc4x.java.canopen.configuration.CANOpenConfiguration;
+import org.apache.plc4x.java.canopen.tag.CANOpenTag;
+import org.apache.plc4x.java.spi.config.Configuration;
+import org.apache.plc4x.java.spi.drivers.ConnectionBase;
+import org.apache.plc4x.java.spi.drivers.DriverBase;
+import org.apache.plc4x.java.spi.transports.api.TransportInstance;
+import org.apache.plc4x.java.utils.auditlog.api.AuditLog;
+
+import java.util.List;
+import java.util.Optional;
+
+public class CANOpenDriver extends DriverBase {
+
+    @Override
+    public String getProtocolCode() {
+        return "canopen";
+    }
+
+    @Override
+    public String getProtocolName() {
+        return "CANopen";
+    }
+
+    @Override
+    protected Class<? extends Configuration> getConfigurationClass() {
+        return CANOpenConfiguration.class;
+    }
+
+    @Override
+    public Optional<String> getDefaultTransportCode() {
+        return Optional.of("can-socketcan");
+    }
+
+    @Override
+    public List<String> getSupportedTransportCodes() {
+        return List.of("can-socketcan", "can-virtualcan", "test");
+    }
+
+    @Override
+    protected boolean canRead() {
+        return true;
+    }
+
+    @Override
+    protected boolean canWrite() {
+        return true;
+    }
+
+    @Override
+    protected boolean canSubscribe() {
+        return true;
+    }
+
+    @Override
+    protected ConnectionBase<?> getConnection(Configuration configuration,
+                                              TransportInstance<?> transportInstance,
+                                              AuditLog auditLog) {
+        return new CANOpenConnection((CANOpenConfiguration) configuration, transportInstance, auditLog);
+    }
+
+    @Override
+    public PlcTag prepareTag(String tagAddress) {
+        return CANOpenTag.of(tagAddress);
+    }
+
+}
diff --git a/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/CANOpenMessageCodec.java b/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/CANOpenMessageCodec.java
new file mode 100644
index 0000000000..dfc6113168
--- /dev/null
+++ b/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/CANOpenMessageCodec.java
@@ -0,0 +1,127 @@
+/*
+ * 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
+ *
+ *   https://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.canopen;
+
+import org.apache.plc4x.java.canopen.readwrite.CANOpenFrame;
+import org.apache.plc4x.java.canopen.readwrite.CANOpenPayload;
+import org.apache.plc4x.java.canopen.readwrite.CANOpenService;
+import org.apache.plc4x.java.spi.buffers.api.exceptions.BufferException;
+import org.apache.plc4x.java.spi.buffers.bytebased.ReadBufferByteBased;
+import org.apache.plc4x.java.spi.buffers.bytebased.WriteBufferByteBased;
+import org.apache.plc4x.java.spi.drivers.MessageCodecBase;
+import org.apache.plc4x.java.spi.drivers.exceptions.MessageCodecException;
+import org.apache.plc4x.java.spi.transports.api.TransportInstance;
+import org.apache.plc4x.java.spi.transports.api.exceptions.TransportException;
+
+import java.util.function.Consumer;
+
+/**
+ * Decodes raw SocketCAN-style 16-byte frames into {@link CANOpenFrame}s and back.
+ *
+ * <p>Wire layout (matches what the {@code can-socketcan} / {@code can-virtualcan}
+ * transports deliver):
+ * <pre>
+ *   canId(4 BE) + dlc(1) + padding(3) + data(8, zero-padded)
+ * </pre>
+ *
+ * <p>The 11-bit CAN identifier is split by CANopen into a 4-bit
+ * {@link CANOpenService} (top nibble) and a 7-bit node id (lower bits) —
+ * we discover the service by finding the {@code CANOpenService} whose
+ * {@code [min,max]} range contains the id, then compute
+ * {@code nodeId = canId - service.min}.</p>
+ */
+public class CANOpenMessageCodec extends MessageCodecBase<CANOpenFrame> {
+
+    private static final int FRAME_SIZE = 16;
+    private static final int EFF_FLAG = 0x80000000;
+    private static final int STANDARD_ID_MASK = 0x7FF;
+    private static final int EXTENDED_ID_MASK = 0x1FFFFFFF;
+
+    public CANOpenMessageCodec(TransportInstance<?> transportInstance, Consumer<CANOpenFrame> messageHandler) {
+        super("CANopen", transportInstance, messageHandler);
+    }
+
+    @Override
+    protected int getMinimumHeaderSize() {
+        return FRAME_SIZE;
+    }
+
+    @Override
+    protected int calculateTotalMessageSize(byte[] header, int availableBytes) {
+        return FRAME_SIZE;
+    }
+
+    @Override
+    protected CANOpenFrame parseMessage(ReadBufferByteBased readBuffer) throws BufferException {
+        byte[] frame = readBuffer.getBytes();
+        int rawId = ((frame[0] & 0xFF) << 24) | ((frame[1] & 0xFF) << 16)
+            | ((frame[2] & 0xFF) << 8) | (frame[3] & 0xFF);
+        boolean extended = (rawId & EFF_FLAG) != 0;
+        int canId = rawId & (extended ? EXTENDED_ID_MASK : STANDARD_ID_MASK);
+        int dlc = frame[4] & 0xFF;
+        if (dlc < 0 || dlc > 8) {
+            dlc = 0;
+        }
+        CANOpenService service = resolveService(canId);
+        if (service == null) {
+            return null;
+        }
+        int nodeId = canId - service.getMin();
+        byte[] data = new byte[dlc];
+        System.arraycopy(frame, 8, data, 0, dlc);
+        // Parse just the payload portion. The frame's nodeId / service come
+        // from the CAN identifier, not the payload bytes, so we can't use the
+        // generated CANOpenFrame.staticParse directly.
+        ReadBufferByteBased payloadBuffer = new ReadBufferByteBased(data, CANOpenConnection.LITTLE_ENDIAN_OPTIONS);
+        CANOpenPayload payload = CANOpenPayload.staticParse(payloadBuffer, service);
+        return new CANOpenFrame((short) nodeId, service, payload);
+    }
+
+    public void sendFrame(CANOpenFrame frame) throws MessageCodecException {
+        byte[] bytes = new byte[FRAME_SIZE];
+        int canId = frame.getService().getMin() + frame.getNodeId();
+        bytes[0] = (byte) ((canId >> 24) & 0xFF);
+        bytes[1] = (byte) ((canId >> 16) & 0xFF);
+        bytes[2] = (byte) ((canId >> 8) & 0xFF);
+        bytes[3] = (byte) (canId & 0xFF);
+        try {
+            CANOpenPayload payload = frame.getPayload();
+            WriteBufferByteBased payloadBuffer = new WriteBufferByteBased(
+                new byte[payload.getLengthInBytes()], CANOpenConnection.LITTLE_ENDIAN_OPTIONS);
+            payload.serialize(payloadBuffer);
+            byte[] payloadBytes = payloadBuffer.getBytes();
+            int dlc = Math.min(payloadBytes.length, 8);
+            bytes[4] = (byte) dlc;
+            System.arraycopy(payloadBytes, 0, bytes, 8, dlc);
+            transportInstance.write(bytes);
+        } catch (BufferException | TransportException e) {
+            throw new MessageCodecException("Failed to send CANopen frame", e);
+        }
+    }
+
+    private static CANOpenService resolveService(int canId) {
+        for (CANOpenService service : CANOpenService.values()) {
+            if (canId >= service.getMin() && canId <= service.getMax()) {
+                return service;
+            }
+        }
+        return null;
+    }
+
+}
diff --git a/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/CANOpenPlcDriver.java b/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/CANOpenPlcDriver.java
deleted file mode 100644
index 6d7d6f08de..0000000000
--- a/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/CANOpenPlcDriver.java
+++ /dev/null
@@ -1,140 +0,0 @@
-/*
- * 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
- *
- *   https://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.canopen;
-
-import org.apache.plc4x.java.canopen.tag.CANOpenTagHandler;
-import org.apache.plc4x.java.spi.configuration.PlcConnectionConfiguration;
-import org.apache.plc4x.java.api.exceptions.PlcRuntimeException;
-import org.apache.plc4x.java.api.model.PlcTag;
-import org.apache.plc4x.java.api.value.PlcValue;
-import org.apache.plc4x.java.can.adapter.CANDriverAdapter;
-import org.apache.plc4x.java.canopen.configuration.CANOpenConfiguration;
-import org.apache.plc4x.java.canopen.context.CANOpenDriverContext;
-import org.apache.plc4x.java.canopen.protocol.CANOpenProtocolLogic;
-import org.apache.plc4x.java.canopen.transport.CANOpenFrameDataHandler;
-import org.apache.plc4x.java.spi.configuration.ConfigurationFactory;
-import org.apache.plc4x.java.spi.connection.CustomProtocolStackConfigurer;
-import org.apache.plc4x.java.spi.connection.GeneratedDriverBase;
-import org.apache.plc4x.java.spi.connection.ProtocolStackConfigurer;
-import org.apache.plc4x.java.spi.generation.Message;
-import org.apache.plc4x.java.spi.optimizer.BaseOptimizer;
-import org.apache.plc4x.java.spi.optimizer.SingleTagOptimizer;
-import org.apache.plc4x.java.spi.transport.Transport;
-import org.apache.plc4x.java.spi.values.DefaultPlcValueHandler;
-import org.apache.plc4x.java.spi.values.PlcValueHandler;
-import org.apache.plc4x.java.spi.values.PlcList;
-import org.apache.plc4x.java.transport.can.CANTransport;
-
-import java.util.Optional;
-
-/**
- */
-public class CANOpenPlcDriver extends GeneratedDriverBase<Message> {
-
-    @Override
-    public String getProtocolCode() {
-        return "canopen";
-    }
-
-    @Override
-    public String getProtocolName() {
-        return "CAN open";
-    }
-
-    @Override
-    protected boolean canRead() {
-        return true;
-    }
-
-    @Override
-    protected boolean canSubscribe() {
-        return true;
-    }
-
-    @Override
-    protected boolean canWrite() {
-        return true;
-    }
-
-    @Override
-    protected Class<? extends PlcConnectionConfiguration> getConfigurationClass() {
-        return CANOpenConfiguration.class;
-    }
-
-    @Override
-    protected Optional<String> getDefaultTransportCode() {
-        return Optional.of("socketcan");
-    }
-
-    @Override
-    protected PlcValueHandler getValueHandler() {
-        return new DefaultPlcValueHandler() {
-            @Override
-            public PlcValue newPlcValue(PlcTag tag, Object[] values) {
-                if (values[0] instanceof PlcList) {
-                    return (PlcList) values[0];
-                }
-                return super.newPlcValue(tag, values);
-            }
-        };
-    }
-
-    /**
-     * This protocol doesn't have a disconnect procedure, so there is no need to wait for a login to finish.
-     * @return false
-     */
-    @Override
-    protected boolean awaitDisconnectComplete() {
-        return false;
-    }
-
-    @Override
-    protected BaseOptimizer getOptimizer() {
-        return new SingleTagOptimizer();
-    }
-
-    @Override
-    protected ProtocolStackConfigurer<Message> getStackConfigurer() {
-        throw new PlcRuntimeException("CANopen driver requires access to transport layer.");
-    }
-
-    @Override
-    protected ProtocolStackConfigurer<Message> getStackConfigurer(Transport transport) {
-        if (!(transport instanceof CANTransport)) {
-            throw new PlcRuntimeException("CANopen driver requires a CAN transport instance");
-        }
-
-        final CANTransport<Message> canTransport = (CANTransport<Message>) transport;
-        return CustomProtocolStackConfigurer.builder(canTransport.getMessageType(), canTransport::getMessageInput)
-            .withProtocol(cfg -> {
-                CANOpenProtocolLogic protocolLogic = new CANOpenProtocolLogic();
-                ConfigurationFactory.configure(cfg, protocolLogic);
-                return new CANDriverAdapter<>(protocolLogic,
-                    canTransport.getMessageType(), canTransport.adapter(),
-                    new CANOpenFrameDataHandler(canTransport::getTransportFrameBuilder),
-                    new CANOpenTagHandler()
-                );
-            })
-            .withDriverContext(cfg -> new CANOpenDriverContext())
-            .withPacketSizeEstimator(configuration1 -> canTransport.getEstimator())
-            .littleEndian()
-            .build();
-    }
-
-}
diff --git a/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/protocol/CANOpenSubscriptionHandle.java b/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/CANOpenSubscriptionHandle.java
similarity index 66%
rename from plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/protocol/CANOpenSubscriptionHandle.java
rename to plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/CANOpenSubscriptionHandle.java
index bfe29cb729..dce81bdea4 100644
--- a/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/protocol/CANOpenSubscriptionHandle.java
+++ b/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/CANOpenSubscriptionHandle.java
@@ -16,19 +16,26 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.plc4x.java.canopen.protocol;
+package org.apache.plc4x.java.canopen;
 
-import org.apache.plc4x.java.canopen.tag.CANOpenSubscriptionTag;
+import org.apache.plc4x.java.api.messages.PlcSubscriptionEvent;
+import org.apache.plc4x.java.api.model.PlcConsumerRegistration;
+import org.apache.plc4x.java.api.model.PlcSubscriptionHandle;
 import org.apache.plc4x.java.canopen.readwrite.CANOpenService;
-import org.apache.plc4x.java.spi.messages.PlcSubscriber;
-import org.apache.plc4x.java.spi.model.DefaultPlcSubscriptionHandle;
+import org.apache.plc4x.java.canopen.tag.CANOpenSubscriptionTag;
+import org.apache.plc4x.java.spi.drivers.functions.PlcSubscriber;
+
+import java.util.Collections;
+import java.util.function.Consumer;
 
-public class CANOpenSubscriptionHandle extends DefaultPlcSubscriptionHandle {
+public class CANOpenSubscriptionHandle implements PlcSubscriptionHandle {
+
+    private final PlcSubscriber subscriber;
     private final String name;
     private final CANOpenSubscriptionTag tag;
 
     public CANOpenSubscriptionHandle(PlcSubscriber subscriber, String name, CANOpenSubscriptionTag tag) {
-        super(subscriber);
+        this.subscriber = subscriber;
         this.name = name;
         this.tag = tag;
     }
@@ -48,8 +55,16 @@ public class CANOpenSubscriptionHandle extends DefaultPlcSubscriptionHandle {
         return tag;
     }
 
+    @Override
+    public PlcConsumerRegistration register(Consumer<PlcSubscriptionEvent> consumer) {
+        return subscriber.registerConsumer(consumer, Collections.singletonList(this));
+    }
+
+    @Override
     public String toString() {
-        return "CANopenSubscriptionHandle [service=" + tag.getService() + ", node=" + intAndHex(tag.getNodeId()) + ", cob=" + intAndHex(tag.getService().getMin() + tag.getNodeId()) + "]";
+        return "CANopenSubscriptionHandle [service=" + tag.getService()
+            + ", node=" + intAndHex(tag.getNodeId())
+            + ", cob=" + intAndHex(tag.getService().getMin() + tag.getNodeId()) + "]";
     }
 
     private static String intAndHex(int val) {
diff --git a/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/api/conversation/canopen/CANConversation.java b/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/api/conversation/canopen/CANConversation.java
deleted file mode 100644
index 75493817e5..0000000000
--- a/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/api/conversation/canopen/CANConversation.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * 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
- *
- *   https://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.canopen.api.conversation.canopen;
-
-import org.apache.plc4x.java.canopen.readwrite.CANOpenFrame;
-import org.apache.plc4x.java.spi.ConversationContext.SendRequestContext;
-
-public interface CANConversation {
-
-    int getNodeId();
-
-    SendRequestContext<CANOpenFrame> send(CANOpenFrame frame);
-
-    void sendToWire(CANOpenFrame frame);
-
-}
-
diff --git a/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/api/conversation/canopen/CANOpenConversationBase.java b/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/api/conversation/canopen/CANOpenConversationBase.java
deleted file mode 100644
index 7ce39b49f7..0000000000
--- a/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/api/conversation/canopen/CANOpenConversationBase.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/*
- * 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
- *
- *   https://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.canopen.api.conversation.canopen;
-
-import io.vavr.control.Either;
-import org.apache.plc4x.java.api.exceptions.PlcException;
-import org.apache.plc4x.java.api.value.PlcValue;
-import org.apache.plc4x.java.canopen.readwrite.CANOpenFrame;
-import org.apache.plc4x.java.canopen.readwrite.*;
-import org.apache.plc4x.java.spi.generation.ByteOrder;
-import org.apache.plc4x.java.spi.generation.ParseException;
-import org.apache.plc4x.java.spi.generation.ReadBufferByteBased;
-
-import java.util.concurrent.CompletableFuture;
-import java.util.function.Predicate;
-
-public abstract class CANOpenConversationBase {
-
-    protected final CANConversation delegate;
-    protected final int nodeId;
-    protected final int answerNodeId;
-
-    public CANOpenConversationBase(CANConversation delegate, int nodeId, int answerNodeId) {
-        this.delegate = delegate;
-        this.nodeId = nodeId;
-        this.answerNodeId = answerNodeId;
-    }
-
-    protected PlcValue decodeFrom(byte[] data, CANOpenDataType type, int length) throws ParseException {
-        return DataItem.staticParse(new ReadBufferByteBased(data, ByteOrder.LITTLE_ENDIAN), type, length);
-    }
-
-    protected <T> void onError(CompletableFuture<T> receiver, CANOpenSDOResponse response, Throwable error) {
-        if (error != null) {
-            receiver.completeExceptionally(error);
-            return;
-        }
-
-        if (response.getResponse() instanceof SDOAbortResponse) {
-            SDOAbortResponse abort = (SDOAbortResponse) response.getResponse();
-            SDOAbort sdoAbort = abort.getAbort();
-            receiver.completeExceptionally(new PlcException("Could not read value. Remote party reported code " + sdoAbort.getCode()));
-        }
-    }
-
-    protected <X extends SDOResponse> Either<SDOAbort, X> unwrap(Class<X> payload, SDOResponse response) {
-        if (response instanceof SDOAbortResponse) {
-            return Either.left(((SDOAbortResponse) response).getAbort());
-        }
-        if (payload.isInstance(response)) {
-            return Either.right((X) response);
-        }
-        throw new RuntimeException("Unexpected payload kind " + response);
-    }
-
-    protected CANOpenFrame createFrame(SDORequest rq) {
-        return new CANOpenFrame((short) nodeId, CANOpenService.RECEIVE_SDO, new CANOpenSDORequest(rq.getCommand(), rq));
-    }
-
-    static class NodeIdPredicate implements Predicate<CANOpenFrame> {
-
-        private final int nodeId;
-
-        NodeIdPredicate(int nodeId) {
-            this.nodeId = nodeId;
-        }
-
-        @Override
-        public boolean test(CANOpenFrame frame) {
-            return frame.getNodeId() == nodeId && frame.getService() == CANOpenService.TRANSMIT_SDO;
-        }
-
-        @Override
-        public String toString() {
-            return "NodeIdPredicate [" + nodeId + "]";
-        }
-    }
-
-    static class TypePredicate<T, X> implements Predicate<X> {
-
-        private final Class<T> type;
-
-        public TypePredicate(Class<T> type) {
-            this.type = type;
-        }
-
-        @Override
-        public boolean test(X value) {
-            return type.isInstance(value);
-        }
-
-        public String toString() {
-            return "Type [" + type + "]";
-        }
-    }
-
-    static class TypeOrAbortPredicate<T extends SDOResponse> extends TypePredicate<T, SDOResponse> {
-
-        public TypeOrAbortPredicate(Class<T> type) {
-            super(type);
-        }
-
-        @Override
-        public boolean test(SDOResponse response) {
-            return super.test(response) || response instanceof SDOAbortResponse;
-        }
-    }
-}
diff --git a/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/api/conversation/canopen/SDODownloadConversation.java b/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/api/conversation/canopen/SDODownloadConversation.java
deleted file mode 100644
index a4684d6b29..0000000000
--- a/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/api/conversation/canopen/SDODownloadConversation.java
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * 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
- *
- *   https://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.canopen.api.conversation.canopen;
-
-import io.vavr.control.Either;
-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.canopen.readwrite.*;
-import org.apache.plc4x.java.canopen.transport.CANOpenAbortException;
-import org.apache.plc4x.java.spi.generation.ByteOrder;
-import org.apache.plc4x.java.spi.generation.SerializationException;
-import org.apache.plc4x.java.spi.generation.WriteBufferByteBased;
-
-import java.util.concurrent.CompletableFuture;
-
-public class SDODownloadConversation extends CANOpenConversationBase {
-
-    private final IndexAddress indexAddress;
-    private final byte[] data;
-
-    public SDODownloadConversation(CANConversation delegate, int nodeId, int answerNodeId, IndexAddress indexAddress, PlcValue value, CANOpenDataType type) {
-        super(delegate, nodeId, answerNodeId);
-        this.indexAddress = indexAddress;
-
-        try {
-            WriteBufferByteBased writeBuffer = new WriteBufferByteBased(DataItem.getLengthInBytes(value, type, null), ByteOrder.LITTLE_ENDIAN);
-            DataItem.staticSerialize(writeBuffer, value, type, null, ByteOrder.LITTLE_ENDIAN);
-            data = writeBuffer.getBytes();
-        } catch (SerializationException e) {
-            throw new PlcRuntimeException("Could not serialize data", e);
-        }
-    }
-
-    public void execute(CompletableFuture<PlcResponseCode> receiver) {
-        if (data.length > 4) {
-            // segmented
-            SDOInitiateSegmentedUploadResponse size = new SDOInitiateSegmentedUploadResponse(data.length);
-            delegate.send(createFrame(new SDOInitiateDownloadRequest(false, true, indexAddress, size)))
-                .check(new NodeIdPredicate(answerNodeId))
-                .onTimeout(receiver::completeExceptionally)
-                .onError((response, error) -> receiver.completeExceptionally(error))
-                .unwrap(CANOpenFrame::getPayload)
-                .only(CANOpenSDOResponse.class)
-                .unwrap(CANOpenSDOResponse::getResponse)
-                .check(new TypeOrAbortPredicate<>(SDOInitiateDownloadResponse.class))
-                .unwrap(payload -> unwrap(SDOInitiateDownloadResponse.class, payload))
-                .handle(either -> {
-                    if (either.isLeft()) {
-                        receiver.completeExceptionally(new CANOpenAbortException("Could not initiate upload", either.getLeft().getCode()));
-                    } else {
-                        SDOInitiateDownloadResponse response = either.get();
-                        if (response.getAddress().equals(indexAddress)) {
-                            put(data, receiver, false, 0);
-                        } else {
-                            // TODO find proper error code in spec
-                            SDOAbort abort = new SDOAbort(indexAddress, 1000);
-                            delegate.sendToWire(createFrame(new SDOAbortRequest(abort)));
-                            receiver.complete(PlcResponseCode.REMOTE_ERROR);
-                        }
-                    }
-                });
-
-            return;
-        }
-
-        // expedited
-        SDOInitiateDownloadRequest rq = new SDOInitiateDownloadRequest(
-            true, true,
-            indexAddress,
-            new SDOInitiateExpeditedUploadResponse(data)
-        );
-
-        delegate.send(createFrame(rq))
-            .check(new NodeIdPredicate(answerNodeId))
-            .onTimeout(receiver::completeExceptionally)
-            .unwrap(CANOpenFrame::getPayload)
-            .only(CANOpenSDOResponse.class)
-            .onError((response, error) -> onError(receiver, response, error))
-            .unwrap(CANOpenSDOResponse::getResponse)
-            .check(new TypeOrAbortPredicate<>(SDOInitiateDownloadResponse.class))
-            .unwrap(payload -> unwrap(SDOInitiateDownloadResponse.class, payload))
-            .handle(either -> {
-                if (either.isLeft()) {
-                    receiver.completeExceptionally(new CANOpenAbortException("Could not initiate upload", either.getLeft().getCode()));
-                } else {
-                    SDOInitiateDownloadResponse response = either.get();
-                    if (response.getCommand() == SDOResponseCommand.INITIATE_DOWNLOAD) {
-                        receiver.complete(PlcResponseCode.OK);
-                    } else {
-                        receiver.complete(PlcResponseCode.REMOTE_ERROR);
-                    }
-                }
-            });
-    }
-
-    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(createFrame(new SDOSegmentDownloadRequest(toggle, remaining <= 7, segment)))
-            .check(new NodeIdPredicate(answerNodeId))
-            .onTimeout(receiver::completeExceptionally)
-            .unwrap(CANOpenFrame::getPayload)
-            .only(CANOpenSDOResponse.class)
-            .onError((response, error) -> onError(receiver, response, error))
-            .unwrap(CANOpenSDOResponse::getResponse)
-            .check(new TypeOrAbortPredicate<>(SDOSegmentDownloadResponse.class))
-            .unwrap(payload -> unwrap(SDOSegmentDownloadResponse.class, payload))
-            .check(sdoSegmentDownloadResponses -> !sdoSegmentDownloadResponses.isLeft())
-            .unwrap(Either::get)
-            .handle(response -> {
-                if (response.getToggle() != toggle) {
-                    receiver.complete(PlcResponseCode.REMOTE_ERROR);
-                    return;
-                }
-
-                if (offset + segment.length == data.length) {
-                    receiver.complete(PlcResponseCode.OK);
-                } else {
-                    put(data, receiver, !toggle, offset + segment.length);
-                }
-            });
-    }
-
-}
diff --git a/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/api/conversation/canopen/SDOUploadConversation.java b/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/api/conversation/canopen/SDOUploadConversation.java
deleted file mode 100644
index 3eecbc424f..0000000000
--- a/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/api/conversation/canopen/SDOUploadConversation.java
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * 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
- *
- *   https://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.canopen.api.conversation.canopen;
-
-import org.apache.plc4x.java.api.exceptions.PlcException;
-import org.apache.plc4x.java.api.value.PlcValue;
-import org.apache.plc4x.java.canopen.api.segmentation.accumulator.ByteStorage;
-import org.apache.plc4x.java.canopen.transport.CANOpenAbortException;
-import org.apache.plc4x.java.canopen.readwrite.CANOpenFrame;
-import org.apache.plc4x.java.canopen.readwrite.*;
-import org.apache.plc4x.java.spi.generation.ParseException;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.util.concurrent.CompletableFuture;
-import java.util.function.BiConsumer;
-
-public class SDOUploadConversation extends CANOpenConversationBase {
-
-    private final Logger logger = LoggerFactory.getLogger(SDOUploadConversation.class);
-    private final IndexAddress address;
-    private final CANOpenDataType type;
-
-    public SDOUploadConversation(CANConversation delegate, int nodeId, int answerNodeId, IndexAddress address, CANOpenDataType type) {
-        super(delegate, nodeId, answerNodeId);
-        this.address = address;
-        this.type = type;
-    }
-
-    public void execute(CompletableFuture<PlcValue> receiver) {
-        SDOInitiateUploadRequest rq = new SDOInitiateUploadRequest(address);
-
-        delegate.send(createFrame(rq))
-            .check(new NodeIdPredicate(answerNodeId))
-            .onTimeout(receiver::completeExceptionally)
-            .unwrap(CANOpenFrame::getPayload)
-            .only(CANOpenSDOResponse.class)
-            .onError((payload, error) -> onError(receiver, payload, error))
-            .unwrap(CANOpenSDOResponse::getResponse)
-            .check(new TypeOrAbortPredicate<>(SDOInitiateUploadResponse.class))
-            .unwrap(payload -> unwrap(SDOInitiateUploadResponse.class, payload))
-            .check(either -> either.isLeft() || either.get().getAddress().equals(address))
-            .handle(either -> {
-                if (either.isLeft()) {
-                    SDOAbort abort = either.getLeft();
-                    receiver.completeExceptionally(new CANOpenAbortException("Could not complete operation", abort.getCode()));
-                } else {
-                    handle(receiver, either.get());
-                }
-            });
-    }
-
-    private void handle(CompletableFuture<PlcValue> receiver, SDOInitiateUploadResponse answer) {
-        BiConsumer<Integer, byte[]> valueCallback = (length, bytes) -> {
-            try {
-                final PlcValue decodedValue = decodeFrom(bytes, type, length);
-                receiver.complete(decodedValue);
-            } catch (ArrayIndexOutOfBoundsException | ParseException e) {
-                receiver.completeExceptionally(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) {
-            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);
-
-            SDOInitiateSegmentedUploadResponse segment = (SDOInitiateSegmentedUploadResponse) answer.getPayload();
-            fetch(storage, valueCallback, receiver, false, Long.valueOf(segment.getBytes()).intValue());
-        } else {
-            receiver.completeExceptionally(new PlcException("Unsupported SDO operation kind."));
-        }
-    }
-
-    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(createFrame(new SDOSegmentUploadRequest(toggle)))
-            .check(new NodeIdPredicate(answerNodeId))
-            .onTimeout(receiver::completeExceptionally)
-            .unwrap(CANOpenFrame::getPayload)
-            .only(CANOpenSDOResponse.class)
-            .onError((payload, error) -> onError(receiver, payload, error))
-            .unwrap(CANOpenSDOResponse::getResponse)
-            .check(new TypeOrAbortPredicate<>(SDOSegmentUploadResponse.class))
-            .unwrap(payload -> unwrap(SDOSegmentUploadResponse.class, payload))
-            .handle(either -> {
-                if (either.isLeft()) {
-                    SDOAbort abort = either.getLeft();
-                    receiver.completeExceptionally(new CANOpenAbortException("Could not complete operation", abort.getCode()));
-                } else {
-                    SDOSegmentUploadResponse response = either.get();
-                    if (response.getToggle() != toggle) {
-                        // TODO find proper error code in specs
-                        receiver.completeExceptionally(new CANOpenAbortException("Remote operation failed", 1000));
-                        SDOAbort abort = new SDOAbort(address, 1000);
-                        delegate.sendToWire(createFrame(new SDOAbortRequest(abort)));
-                        return;
-                    }
-
-                    storage.append(either.get());
-                    if (response.getLast()) {
-                        // validate size
-                        logger.trace("Completed reading of data from {}/{}, collected {}, wanted {}", Integer.toHexString(address.getIndex()), Integer.toHexString(address.getSubindex()), storage.size(), size);
-                        valueCallback.accept(Long.valueOf(size).intValue(), storage.get());
-                    } else {
-                        logger.trace("Continue reading of data from {}/{}, collected {}, wanted {}", Integer.toHexString(address.getIndex()), Integer.toHexString(address.getSubindex()), storage.size(), size);
-                        fetch(storage, valueCallback, receiver, !toggle, size);
-                    }
-                }
-            });
-    }
-
-}
diff --git a/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/api/segmentation/accumulator/ByteStorage.java b/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/api/segmentation/accumulator/ByteStorage.java
deleted file mode 100644
index faca8b4701..0000000000
--- a/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/api/segmentation/accumulator/ByteStorage.java
+++ /dev/null
@@ -1,95 +0,0 @@
-/*
- * 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
- *
- *   https://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.canopen.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];
-            }));
-        }
-    }
-
-    public static class SDODownloadStorage extends ByteStorage<SDORequest> {
-        public SDODownloadStorage() {
-            super((sdoRequest -> {
-                if (sdoRequest instanceof SDOSegmentDownloadRequest) {
-                    return ((SDOSegmentDownloadRequest) sdoRequest).getData();
-                }
-                if (sdoRequest instanceof  SDOInitiateDownloadRequest) {
-                    SDOInitiateDownloadRequest initiate = (SDOInitiateDownloadRequest) sdoRequest;
-
-                    if (initiate.getPayload() instanceof SDOInitiateExpeditedUploadResponse) {
-                        return ((SDOInitiateExpeditedUploadResponse) initiate.getPayload()).getData();
-                    }
-                }
-                return new byte[0];
-            }));
-        }
-    }
-
-}
diff --git a/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/api/segmentation/accumulator/Storage.java b/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/api/segmentation/accumulator/Storage.java
deleted file mode 100644
index cf0c618e65..0000000000
--- a/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/api/segmentation/accumulator/Storage.java
+++ /dev/null
@@ -1,50 +0,0 @@
-/*
- * 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
- *
- *   https://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.canopen.api.segmentation.accumulator;
-
-/**
- * A storage which is called for each received segment.
- *
- * @param <T> Type of frame.
- * @param <R> Type of result.
- */
-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/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/configuration/CANOpenConfiguration.java b/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/configuration/CANOpenConfiguration.java
index f518450f15..a8585ae785 100644
--- a/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/configuration/CANOpenConfiguration.java
+++ b/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/configuration/CANOpenConfiguration.java
@@ -18,18 +18,20 @@
  */
 package org.apache.plc4x.java.canopen.configuration;
 
-import org.apache.plc4x.java.spi.configuration.PlcConnectionConfiguration;
-import org.apache.plc4x.java.spi.configuration.annotations.ConfigurationParameter;
-import org.apache.plc4x.java.spi.configuration.annotations.Description;
-import org.apache.plc4x.java.spi.configuration.annotations.defaults.IntDefaultValue;
+import org.apache.plc4x.java.spi.config.Configuration;
+import org.apache.plc4x.java.spi.config.annotations.ConfigurationParameter;
+import org.apache.plc4x.java.spi.config.annotations.Description;
+import org.apache.plc4x.java.spi.config.annotations.defaults.BooleanDefaultValue;
+import org.apache.plc4x.java.spi.config.annotations.defaults.IntDefaultValue;
 
-public class CANOpenConfiguration implements PlcConnectionConfiguration {
+public class CANOpenConfiguration implements Configuration {
 
     @ConfigurationParameter("node-id")
     @Description("CAN node identifier. Depending on used CAN version it might be 11 or 29 bit unsigned int.")
     private int nodeId;
 
     @ConfigurationParameter
+    @BooleanDefaultValue(false)
     @Description("Forces PLC4X to send CANopen heartbeat (NMT) messages to the bus.")
     private boolean heartbeat;
 
diff --git a/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/context/CANOpenDriverContext.java b/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/context/CANOpenDriverContext.java
deleted file mode 100644
index 825247fd83..0000000000
--- a/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/context/CANOpenDriverContext.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * 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
- *
- *   https://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.canopen.context;
-
-import org.apache.plc4x.java.canopen.listener.CompositeCallback;
-import org.apache.plc4x.java.spi.context.DriverContext;
-
-public class CANOpenDriverContext implements DriverContext {
-
-    public final static CompositeCallback CALLBACK = new CompositeCallback();
-
-}
diff --git a/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/conversation/CANConversation.java b/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/conversation/CANConversation.java
new file mode 100644
index 0000000000..5b8fa0057e
--- /dev/null
+++ b/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/conversation/CANConversation.java
@@ -0,0 +1,57 @@
+/*
+ * 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
+ *
+ *   https://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.canopen.conversation;
+
+import org.apache.plc4x.java.canopen.readwrite.CANOpenFrame;
+
+import java.time.Duration;
+import java.util.concurrent.CompletableFuture;
+import java.util.function.Predicate;
+
+/**
+ * Minimal request/response abstraction used by the multi-turn SDO conversations.
+ *
+ * <p>The new SPI's {@code ConnectionBase} doesn't ship a fluent ConversationContext
+ * like the old SPI did. This interface gives the SDO upload/download conversations
+ * just two primitives:
+ *
+ * <ul>
+ *   <li>{@link #sendToWire(CANOpenFrame)} — fire-and-forget a frame.</li>
+ *   <li>{@link #expect(Predicate, Duration)} — return a future that completes
+ *       with the next incoming frame matching {@code predicate}, or completes
+ *       exceptionally with {@link java.util.concurrent.TimeoutException} after
+ *       {@code timeout}.</li>
+ * </ul>
+ *
+ * <p>Multi-step protocols become straightforward CompletableFuture chains: send,
+ * expect, decide, send again, expect again.</p>
+ */
+public interface CANConversation {
+
+    /** Sends a frame on the bus; does not wait for a response. */
+    void sendToWire(CANOpenFrame frame);
+
+    /**
+     * Registers a one-shot listener for the next frame matching {@code predicate}.
+     * The returned future completes with that frame, or completes exceptionally
+     * with {@link java.util.concurrent.TimeoutException} after {@code timeout}.
+     */
+    CompletableFuture<CANOpenFrame> expect(Predicate<CANOpenFrame> predicate, Duration timeout);
+
+}
diff --git a/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/conversation/CANTransportConversation.java b/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/conversation/CANTransportConversation.java
deleted file mode 100644
index c62f5507e0..0000000000
--- a/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/conversation/CANTransportConversation.java
+++ /dev/null
@@ -1,54 +0,0 @@
-/*
- * 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
- *
- *   https://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.canopen.conversation;
-
-import org.apache.plc4x.java.canopen.api.conversation.canopen.CANConversation;
-import org.apache.plc4x.java.canopen.readwrite.CANOpenFrame;
-import org.apache.plc4x.java.spi.ConversationContext;
-import org.apache.plc4x.java.spi.ConversationContext.SendRequestContext;
-
-import java.time.Duration;
-
-public class CANTransportConversation implements CANConversation {
-
-    private final int nodeId;
-    private final ConversationContext<CANOpenFrame> context;
-    private final int timeout;
-
-    public CANTransportConversation(int nodeId, ConversationContext<CANOpenFrame> context, int timeout) {
-        this.nodeId = nodeId;
-        this.context = context;
-        this.timeout = timeout;
-    }
-
-    @Override
-    public int getNodeId() {
-        return nodeId;
-    }
-
-    public SendRequestContext<CANOpenFrame> send(CANOpenFrame frame) {
-        return context.sendRequest(frame)
-            .expectResponse(CANOpenFrame.class, Duration.ofMillis(timeout));
-    }
-
-    public void sendToWire(CANOpenFrame frame) {
-        context.sendToWire(frame);
-    }
-
-}
diff --git a/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/listener/CompositeCallback.java b/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/conversation/ConversationPredicates.java
similarity index 54%
rename from plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/listener/CompositeCallback.java
rename to plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/conversation/ConversationPredicates.java
index c6b41d8377..a9b7de96c7 100644
--- a/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/listener/CompositeCallback.java
+++ b/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/conversation/ConversationPredicates.java
@@ -16,28 +16,25 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-package org.apache.plc4x.java.canopen.listener;
+package org.apache.plc4x.java.canopen.conversation;
 
 import org.apache.plc4x.java.canopen.readwrite.CANOpenFrame;
+import org.apache.plc4x.java.canopen.readwrite.CANOpenSDOResponse;
+import org.apache.plc4x.java.canopen.readwrite.CANOpenService;
 
-import java.util.List;
-import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.function.Predicate;
 
-public class CompositeCallback implements Callback {
+/** Reusable predicates for {@link CANConversation#expect(Predicate, java.time.Duration)}. */
+public final class ConversationPredicates {
 
-    private final List<Callback> callbacks = new CopyOnWriteArrayList<>();
-
-    @Override
-    public void receive(CANOpenFrame frame) {
-        callbacks.forEach(callback -> callback.receive(frame));
+    private ConversationPredicates() {
     }
 
-    public boolean addCallback(Callback callback) {
-        return callbacks.add(callback);
+    /** Matches a TRANSMIT_SDO frame from the given node id whose payload is a {@link CANOpenSDOResponse}. */
+    public static Predicate<CANOpenFrame> sdoTransmitFrom(int nodeId) {
+        return frame -> frame.getNodeId() == nodeId
+            && frame.getService() == CANOpenService.TRANSMIT_SDO
+            && frame.getPayload() instanceof CANOpenSDOResponse;
     }
 
-    public boolean removeCallback(Callback callback) {
-        return callbacks.remove(callback);
-    }
 }
-
diff --git a/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/conversation/SDODownloadConversation.java b/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/conversation/SDODownloadConversation.java
new file mode 100644
index 0000000000..50bf7ef91b
--- /dev/null
+++ b/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/conversation/SDODownloadConversation.java
@@ -0,0 +1,196 @@
+/*
+ * 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
+ *
+ *   https://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.canopen.conversation;
+
+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.canopen.CANOpenConnection;
+import org.apache.plc4x.java.canopen.readwrite.CANOpenDataType;
+import org.apache.plc4x.java.canopen.readwrite.CANOpenFrame;
+import org.apache.plc4x.java.canopen.readwrite.CANOpenPayload;
+import org.apache.plc4x.java.canopen.readwrite.CANOpenSDORequest;
+import org.apache.plc4x.java.canopen.readwrite.CANOpenSDOResponse;
+import org.apache.plc4x.java.canopen.readwrite.CANOpenService;
+import org.apache.plc4x.java.canopen.readwrite.DataItem;
+import org.apache.plc4x.java.canopen.readwrite.IndexAddress;
+import org.apache.plc4x.java.canopen.readwrite.SDOAbort;
+import org.apache.plc4x.java.canopen.readwrite.SDOAbortRequest;
+import org.apache.plc4x.java.canopen.readwrite.SDOAbortResponse;
+import org.apache.plc4x.java.canopen.readwrite.SDOInitiateDownloadRequest;
+import org.apache.plc4x.java.canopen.readwrite.SDOInitiateDownloadResponse;
+import org.apache.plc4x.java.canopen.readwrite.SDOInitiateExpeditedUploadResponse;
+import org.apache.plc4x.java.canopen.readwrite.SDOInitiateSegmentedUploadResponse;
+import org.apache.plc4x.java.canopen.readwrite.SDORequestCommand;
+import org.apache.plc4x.java.canopen.readwrite.SDOResponse;
+import org.apache.plc4x.java.canopen.readwrite.SDOResponseCommand;
+import org.apache.plc4x.java.canopen.readwrite.SDOSegmentDownloadRequest;
+import org.apache.plc4x.java.canopen.readwrite.SDOSegmentDownloadResponse;
+import org.apache.plc4x.java.canopen.transport.CANOpenAbortException;
+import org.apache.plc4x.java.spi.buffers.api.exceptions.BufferException;
+import org.apache.plc4x.java.spi.buffers.bytebased.WriteBufferByteBased;
+
+import java.time.Duration;
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * Performs an SDO download (write) — expedited (≤4 byte value in one frame) or
+ * segmented (announce size, then push 7-byte data segments until done).
+ */
+public class SDODownloadConversation {
+
+    private final CANConversation conversation;
+    private final int nodeId;
+    private final int answerNodeId;
+    private final IndexAddress indexAddress;
+    private final byte[] data;
+    private final Duration timeout;
+
+    public SDODownloadConversation(CANConversation conversation, int nodeId, int answerNodeId,
+                                   IndexAddress indexAddress, PlcValue value, CANOpenDataType type,
+                                   Duration timeout) {
+        this.conversation = conversation;
+        this.nodeId = nodeId;
+        this.answerNodeId = answerNodeId;
+        this.indexAddress = indexAddress;
+        this.timeout = timeout;
+        try {
+            int byteLen = DataItem.getLengthInBytes(value, type, value.getLength());
+            WriteBufferByteBased buffer = new WriteBufferByteBased(new byte[byteLen], CANOpenConnection.LITTLE_ENDIAN_OPTIONS);
+            DataItem.staticSerialize(buffer, value, type, value.getLength());
+            data = buffer.getBytes();
+        } catch (BufferException e) {
+            throw new PlcRuntimeException("Could not serialize SDO write payload", e);
+        }
+    }
+
+    public void execute(CompletableFuture<PlcResponseCode> receiver) {
+        if (data.length > 4) {
+            // Segmented path — first announce the size, then push 7-byte chunks.
+            SDOInitiateSegmentedUploadResponse size = new SDOInitiateSegmentedUploadResponse((long) data.length);
+            CompletableFuture<CANOpenFrame> initResp = conversation.expect(
+                ConversationPredicates.sdoTransmitFrom(answerNodeId), timeout);
+            conversation.sendToWire(createFrame(new CANOpenSDORequest(SDORequestCommand.INITIATE_DOWNLOAD,
+                new SDOInitiateDownloadRequest(false, true, indexAddress, size))));
+
+            initResp.whenComplete((frame, error) -> {
+                if (error != null) {
+                    receiver.completeExceptionally(error);
+                    return;
+                }
+                SDOResponse response = ((CANOpenSDOResponse) frame.getPayload()).getResponse();
+                if (response instanceof SDOAbortResponse) {
+                    SDOAbort abort = ((SDOAbortResponse) response).getAbort();
+                    receiver.completeExceptionally(new CANOpenAbortException(
+                        "Could not initiate download", abort.getCode()));
+                    return;
+                }
+                if (!(response instanceof SDOInitiateDownloadResponse)) {
+                    receiver.complete(PlcResponseCode.REMOTE_ERROR);
+                    return;
+                }
+                SDOInitiateDownloadResponse initResponse = (SDOInitiateDownloadResponse) response;
+                if (!initResponse.getAddress().equals(indexAddress)) {
+                    // Mismatched address — abort and report.
+                    conversation.sendToWire(createFrame(new CANOpenSDORequest(SDORequestCommand.ABORT,
+                        new SDOAbortRequest(new SDOAbort(indexAddress, 1000L)))));
+                    receiver.complete(PlcResponseCode.REMOTE_ERROR);
+                    return;
+                }
+                put(receiver, false, 0);
+            });
+            return;
+        }
+
+        // Expedited path — single frame carries up to 4 bytes of payload.
+        SDOInitiateDownloadRequest rq = new SDOInitiateDownloadRequest(
+            true, true, indexAddress, new SDOInitiateExpeditedUploadResponse(data));
+
+        CompletableFuture<CANOpenFrame> resp = conversation.expect(
+            ConversationPredicates.sdoTransmitFrom(answerNodeId), timeout);
+        conversation.sendToWire(createFrame(new CANOpenSDORequest(SDORequestCommand.INITIATE_DOWNLOAD, rq)));
+
+        resp.whenComplete((frame, error) -> {
+            if (error != null) {
+                receiver.completeExceptionally(error);
+                return;
+            }
+            SDOResponse response = ((CANOpenSDOResponse) frame.getPayload()).getResponse();
+            if (response instanceof SDOAbortResponse) {
+                SDOAbort abort = ((SDOAbortResponse) response).getAbort();
+                receiver.completeExceptionally(new CANOpenAbortException(
+                    "Could not initiate download", abort.getCode()));
+                return;
+            }
+            if (response instanceof SDOInitiateDownloadResponse
+                && ((SDOInitiateDownloadResponse) response).getCommand() == SDOResponseCommand.INITIATE_DOWNLOAD) {
+                receiver.complete(PlcResponseCode.OK);
+            } else {
+                receiver.complete(PlcResponseCode.REMOTE_ERROR);
+            }
+        });
+    }
+
+    private void put(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);
+        boolean last = remaining <= 7;
+
+        CompletableFuture<CANOpenFrame> resp = conversation.expect(
+            ConversationPredicates.sdoTransmitFrom(answerNodeId), timeout);
+        conversation.sendToWire(createFrame(new CANOpenSDORequest(SDORequestCommand.SEGMENT_DOWNLOAD,
+            new SDOSegmentDownloadRequest(toggle, last, segment))));
+
+        resp.whenComplete((frame, error) -> {
+            if (error != null) {
+                receiver.completeExceptionally(error);
+                return;
+            }
+            SDOResponse response = ((CANOpenSDOResponse) frame.getPayload()).getResponse();
+            if (response instanceof SDOAbortResponse) {
+                SDOAbort abort = ((SDOAbortResponse) response).getAbort();
+                receiver.completeExceptionally(new CANOpenAbortException(
+                    "Remote aborted segmented download", abort.getCode()));
+                return;
+            }
+            if (!(response instanceof SDOSegmentDownloadResponse)) {
+                receiver.complete(PlcResponseCode.REMOTE_ERROR);
+                return;
+            }
+            SDOSegmentDownloadResponse seg = (SDOSegmentDownloadResponse) response;
+            if (seg.getToggle() != toggle) {
+                receiver.complete(PlcResponseCode.REMOTE_ERROR);
+                return;
+            }
+            int newOffset = offset + segment.length;
+            if (newOffset >= data.length) {
+                receiver.complete(PlcResponseCode.OK);
+            } else {
+                put(receiver, !toggle, newOffset);
+            }
+        });
+    }
+
+    private CANOpenFrame createFrame(CANOpenPayload payload) {
+        return new CANOpenFrame((short) nodeId, CANOpenService.RECEIVE_SDO, payload);
+    }
+
+}
diff --git a/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/conversation/SDOUploadConversation.java b/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/conversation/SDOUploadConversation.java
new file mode 100644
index 0000000000..9fc4fdf8fe
--- /dev/null
+++ b/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/conversation/SDOUploadConversation.java
@@ -0,0 +1,192 @@
+/*
+ * 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
+ *
+ *   https://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.canopen.conversation;
+
+import org.apache.plc4x.java.api.exceptions.PlcException;
+import org.apache.plc4x.java.api.value.PlcValue;
+import org.apache.plc4x.java.canopen.CANOpenConnection;
+import org.apache.plc4x.java.canopen.readwrite.CANOpenFrame;
+import org.apache.plc4x.java.canopen.readwrite.CANOpenPayload;
+import org.apache.plc4x.java.canopen.readwrite.CANOpenSDORequest;
+import org.apache.plc4x.java.canopen.readwrite.CANOpenSDOResponse;
+import org.apache.plc4x.java.canopen.readwrite.CANOpenService;
+import org.apache.plc4x.java.canopen.readwrite.CANOpenDataType;
+import org.apache.plc4x.java.canopen.readwrite.DataItem;
+import org.apache.plc4x.java.canopen.readwrite.IndexAddress;
+import org.apache.plc4x.java.canopen.readwrite.SDOAbort;
+import org.apache.plc4x.java.canopen.readwrite.SDOAbortRequest;
+import org.apache.plc4x.java.canopen.readwrite.SDOAbortResponse;
+import org.apache.plc4x.java.canopen.readwrite.SDOInitiateExpeditedUploadResponse;
+import org.apache.plc4x.java.canopen.readwrite.SDOInitiateSegmentedUploadResponse;
+import org.apache.plc4x.java.canopen.readwrite.SDOInitiateUploadRequest;
+import org.apache.plc4x.java.canopen.readwrite.SDOInitiateUploadResponse;
+import org.apache.plc4x.java.canopen.readwrite.SDORequestCommand;
+import org.apache.plc4x.java.canopen.readwrite.SDOResponse;
+import org.apache.plc4x.java.canopen.readwrite.SDOSegmentUploadRequest;
+import org.apache.plc4x.java.canopen.readwrite.SDOSegmentUploadResponse;
+import org.apache.plc4x.java.canopen.transport.CANOpenAbortException;
+import org.apache.plc4x.java.spi.buffers.api.exceptions.BufferException;
+import org.apache.plc4x.java.spi.buffers.bytebased.ReadBufferByteBased;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.time.Duration;
+import java.util.concurrent.CompletableFuture;
+
+/**
+ * Performs an SDO upload (read) — either expedited (≤4 byte value in one frame) or
+ * segmented (response says "N bytes, fetch in segments").
+ */
+public class SDOUploadConversation {
+
+    private static final Logger LOGGER = LoggerFactory.getLogger(SDOUploadConversation.class);
+
+    private final CANConversation conversation;
+    private final int nodeId;
+    private final int answerNodeId;
+    private final IndexAddress address;
+    private final CANOpenDataType type;
+    private final Duration timeout;
+
+    public SDOUploadConversation(CANConversation conversation, int nodeId, int answerNodeId,
+                                 IndexAddress address, CANOpenDataType type, Duration timeout) {
+        this.conversation = conversation;
+        this.nodeId = nodeId;
+        this.answerNodeId = answerNodeId;
+        this.address = address;
+        this.type = type;
+        this.timeout = timeout;
+    }
+
+    public void execute(CompletableFuture<PlcValue> receiver) {
+        SDOInitiateUploadRequest rq = new SDOInitiateUploadRequest(address);
+        CompletableFuture<CANOpenFrame> initialResponse = conversation.expect(
+            ConversationPredicates.sdoTransmitFrom(answerNodeId), timeout);
+        conversation.sendToWire(createFrame(new CANOpenSDORequest(SDORequestCommand.INITIATE_UPLOAD, rq)));
+
+        initialResponse.whenComplete((frame, error) -> {
+            if (error != null) {
+                receiver.completeExceptionally(error);
+                return;
+            }
+            try {
+                SDOResponse response = ((CANOpenSDOResponse) frame.getPayload()).getResponse();
+                if (response instanceof SDOAbortResponse) {
+                    SDOAbort abort = ((SDOAbortResponse) response).getAbort();
+                    receiver.completeExceptionally(new CANOpenAbortException(
+                        "Could not initiate upload", abort.getCode()));
+                    return;
+                }
+                if (!(response instanceof SDOInitiateUploadResponse)) {
+                    receiver.completeExceptionally(new PlcException(
+                        "Unexpected SDO response: " + response.getClass().getSimpleName()));
+                    return;
+                }
+                SDOInitiateUploadResponse upload = (SDOInitiateUploadResponse) response;
+                if (!upload.getAddress().equals(address)) {
+                    receiver.completeExceptionally(new PlcException(
+                        "SDO upload response for the wrong address: " + upload.getAddress()));
+                    return;
+                }
+                handle(receiver, upload);
+            } catch (Exception e) {
+                receiver.completeExceptionally(e);
+            }
+        });
+    }
+
+    private void handle(CompletableFuture<PlcValue> receiver, SDOInitiateUploadResponse answer) {
+        if (answer.getExpedited() && answer.getIndicated()
+            && answer.getPayload() instanceof SDOInitiateExpeditedUploadResponse) {
+            SDOInitiateExpeditedUploadResponse payload = (SDOInitiateExpeditedUploadResponse) answer.getPayload();
+            completeWithValue(receiver, payload.getData(), payload.getData().length);
+        } else if (answer.getPayload() instanceof SDOInitiateSegmentedUploadResponse) {
+            SDOInitiateSegmentedUploadResponse segment = (SDOInitiateSegmentedUploadResponse) answer.getPayload();
+            int totalSize = (int) segment.getBytes();
+            fetch(receiver, new byte[totalSize], 0, totalSize, false);
+        } else {
+            receiver.completeExceptionally(new PlcException("Unsupported SDO upload kind."));
+        }
+    }
+
+    private void fetch(CompletableFuture<PlcValue> receiver, byte[] storage, int offset, int totalSize, boolean toggle) {
+        CompletableFuture<CANOpenFrame> nextSegment = conversation.expect(
+            ConversationPredicates.sdoTransmitFrom(answerNodeId), timeout);
+        conversation.sendToWire(createFrame(new CANOpenSDORequest(SDORequestCommand.SEGMENT_UPLOAD,
+            new SDOSegmentUploadRequest(toggle))));
+
+        nextSegment.whenComplete((frame, error) -> {
+            if (error != null) {
+                receiver.completeExceptionally(error);
+                return;
+            }
+            try {
+                SDOResponse response = ((CANOpenSDOResponse) frame.getPayload()).getResponse();
+                if (response instanceof SDOAbortResponse) {
+                    SDOAbort abort = ((SDOAbortResponse) response).getAbort();
+                    receiver.completeExceptionally(new CANOpenAbortException(
+                        "Remote aborted segmented upload", abort.getCode()));
+                    return;
+                }
+                if (!(response instanceof SDOSegmentUploadResponse)) {
+                    receiver.completeExceptionally(new PlcException(
+                        "Unexpected SDO segment response: " + response.getClass().getSimpleName()));
+                    return;
+                }
+                SDOSegmentUploadResponse seg = (SDOSegmentUploadResponse) response;
+                if (seg.getToggle() != toggle) {
+                    // Toggle mismatch — abort and surface the error.
+                    conversation.sendToWire(createFrame(new CANOpenSDORequest(SDORequestCommand.ABORT,
+                        new SDOAbortRequest(new SDOAbort(address, 1000L)))));
+                    receiver.completeExceptionally(new CANOpenAbortException("Toggle mismatch", 1000));
+                    return;
+                }
+                byte[] data = seg.getData();
+                int newOffset = offset + data.length;
+                if (newOffset > storage.length) {
+                    // More bytes than the initial response promised — clamp.
+                    newOffset = storage.length;
+                }
+                System.arraycopy(data, 0, storage, offset, newOffset - offset);
+                if (seg.getLast()) {
+                    completeWithValue(receiver, storage, totalSize);
+                } else {
+                    fetch(receiver, storage, newOffset, totalSize, !toggle);
+                }
+            } catch (Exception e) {
+                receiver.completeExceptionally(e);
+            }
+        });
+    }
+
+    private void completeWithValue(CompletableFuture<PlcValue> receiver, byte[] bytes, int length) {
+        try {
+            ReadBufferByteBased buffer = new ReadBufferByteBased(bytes, CANOpenConnection.LITTLE_ENDIAN_OPTIONS);
+            PlcValue value = DataItem.staticParse(buffer, type, length);
+            receiver.complete(value);
+        } catch (BufferException | ArrayIndexOutOfBoundsException e) {
+            receiver.completeExceptionally(e);
+        }
+    }
+
+    private CANOpenFrame createFrame(CANOpenPayload payload) {
+        return new CANOpenFrame((short) nodeId, CANOpenService.RECEIVE_SDO, payload);
+    }
+
+}
diff --git a/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/listener/Callback.java b/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/listener/Callback.java
deleted file mode 100644
index 0520961193..0000000000
--- a/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/listener/Callback.java
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * 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
- *
- *   https://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.canopen.listener;
-
-import org.apache.plc4x.java.canopen.readwrite.CANOpenFrame;
-
-public interface Callback {
-    void receive(CANOpenFrame frame);
-}
-
diff --git a/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/protocol/CANOpenProtocolLogic.java b/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/protocol/CANOpenProtocolLogic.java
deleted file mode 100644
index 9124cab003..0000000000
--- a/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/protocol/CANOpenProtocolLogic.java
+++ /dev/null
@@ -1,456 +0,0 @@
-/*
- * 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
- *
- *   https://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.canopen.protocol;
-
-import org.apache.plc4x.java.api.exceptions.PlcRuntimeException;
-import org.apache.plc4x.java.api.messages.PlcReadRequest;
-import org.apache.plc4x.java.api.messages.PlcReadResponse;
-import org.apache.plc4x.java.api.messages.PlcSubscriptionEvent;
-import org.apache.plc4x.java.api.messages.PlcSubscriptionRequest;
-import org.apache.plc4x.java.api.messages.PlcSubscriptionResponse;
-import org.apache.plc4x.java.api.messages.PlcUnsubscriptionRequest;
-import org.apache.plc4x.java.api.messages.PlcUnsubscriptionResponse;
-import org.apache.plc4x.java.api.messages.PlcWriteRequest;
-import org.apache.plc4x.java.api.messages.PlcWriteResponse;
-import org.apache.plc4x.java.api.model.PlcConsumerRegistration;
-import org.apache.plc4x.java.api.model.PlcTag;
-import org.apache.plc4x.java.api.model.PlcSubscriptionHandle;
-import org.apache.plc4x.java.api.types.PlcResponseCode;
-import org.apache.plc4x.java.api.types.PlcSubscriptionType;
-import org.apache.plc4x.java.api.value.PlcValue;
-import org.apache.plc4x.java.can.adapter.Plc4xCANProtocolBase;
-import org.apache.plc4x.java.canopen.tag.*;
-import org.apache.plc4x.java.canopen.transport.CANOpenAbortException;
-import org.apache.plc4x.java.canopen.readwrite.CANOpenFrame;
-import org.apache.plc4x.java.canopen.api.conversation.canopen.CANConversation;
-import org.apache.plc4x.java.canopen.api.conversation.canopen.SDODownloadConversation;
-import org.apache.plc4x.java.canopen.api.conversation.canopen.SDOUploadConversation;
-import org.apache.plc4x.java.canopen.configuration.CANOpenConfiguration;
-import org.apache.plc4x.java.canopen.context.CANOpenDriverContext;
-import org.apache.plc4x.java.canopen.conversation.CANTransportConversation;
-import org.apache.plc4x.java.canopen.readwrite.CANOpenHeartbeatPayload;
-import org.apache.plc4x.java.canopen.readwrite.CANOpenNetworkPayload;
-import org.apache.plc4x.java.canopen.readwrite.CANOpenPDO;
-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.DataItem;
-import org.apache.plc4x.java.canopen.readwrite.CANOpenService;
-import org.apache.plc4x.java.canopen.readwrite.NMTState;
-import org.apache.plc4x.java.canopen.readwrite.NMTStateRequest;
-import org.apache.plc4x.java.spi.ConversationContext;
-import org.apache.plc4x.java.spi.configuration.HasConfiguration;
-import org.apache.plc4x.java.spi.connection.PlcTagHandler;
-import org.apache.plc4x.java.spi.context.DriverContext;
-import org.apache.plc4x.java.spi.generation.*;
-import org.apache.plc4x.java.spi.messages.DefaultPlcReadResponse;
-import org.apache.plc4x.java.spi.messages.DefaultPlcSubscriptionEvent;
-import org.apache.plc4x.java.spi.messages.DefaultPlcSubscriptionRequest;
-import org.apache.plc4x.java.spi.messages.DefaultPlcSubscriptionResponse;
-import org.apache.plc4x.java.spi.messages.DefaultPlcUnsubscriptionResponse;
-import org.apache.plc4x.java.spi.messages.DefaultPlcWriteRequest;
-import org.apache.plc4x.java.spi.messages.DefaultPlcWriteResponse;
-import org.apache.plc4x.java.spi.messages.PlcSubscriber;
-import org.apache.plc4x.java.spi.messages.utils.DefaultPlcResponseItem;
-import org.apache.plc4x.java.spi.messages.utils.PlcResponseItem;
-import org.apache.plc4x.java.spi.model.DefaultPlcConsumerRegistration;
-import org.apache.plc4x.java.spi.model.DefaultPlcSubscriptionTag;
-import org.apache.plc4x.java.spi.model.DefaultPlcSubscriptionHandle;
-import org.apache.plc4x.java.spi.transaction.RequestTransactionManager;
-import org.apache.plc4x.java.spi.values.PlcLINT;
-import org.apache.plc4x.java.spi.values.PlcNull;
-import org.apache.plc4x.java.spi.values.PlcStruct;
-import org.apache.plc4x.java.spi.values.PlcUSINT;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import java.time.Duration;
-import java.time.Instant;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Timer;
-import java.util.TimerTask;
-import java.util.concurrent.CompletableFuture;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.function.Consumer;
-
-public class CANOpenProtocolLogic extends Plc4xCANProtocolBase<CANOpenFrame>
-    implements HasConfiguration<CANOpenConfiguration>, PlcSubscriber {
-
-    private static final Duration REQUEST_TIMEOUT = Duration.ofSeconds(10L);
-    private final Logger logger = LoggerFactory.getLogger(CANOpenProtocolLogic.class);
-
-    private CANOpenConfiguration configuration;
-    private RequestTransactionManager tm;
-    private Timer heartbeat;
-    private CANOpenDriverContext canContext;
-    private CANConversation conversation;
-
-    private final Map<DefaultPlcConsumerRegistration, Consumer<PlcSubscriptionEvent>> consumers = new ConcurrentHashMap<>();
-
-    @Override
-    public void setConfiguration(CANOpenConfiguration configuration) {
-        this.configuration = configuration;
-    }
-
-    @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 PlcTagHandler getTagHandler() {
-        return new CANOpenTagHandler();
-    }
-
-    @Override
-    public void close(ConversationContext<CANOpenFrame> context) {
-        tm.shutdown();
-    }
-
-    @Override
-    public void onConnect(ConversationContext<CANOpenFrame> context) {
-        try {
-            if (configuration.isHeartbeat()) {
-                context.sendToWire(createFrame(new CANOpenHeartbeatPayload(NMTState.BOOTED_UP)));
-
-                this.heartbeat = new Timer("can-heartbeat");
-                this.heartbeat.scheduleAtFixedRate(new TimerTask() {
-                    @Override
-                    public void run() {
-                        try {
-                            context.sendToWire(createFrame(new CANOpenHeartbeatPayload(NMTState.OPERATIONAL)));
-                        } catch (ParseException e) {
-                            throw new PlcRuntimeException(e);
-                        }
-                    }
-                }, 10000, 10000);
-            }
-            context.fireConnected();
-        } catch (ParseException e) {
-            throw new PlcRuntimeException(e);
-        }
-    }
-
-    @Override
-    public void setConversationContext(ConversationContext<CANOpenFrame> conversationContext) {
-        super.setConversationContext(conversationContext);
-        this.conversation = new CANTransportConversation(configuration.getNodeId(), conversationContext, configuration.getRequestTimeout());
-    }
-
-    private CANOpenFrame createFrame(CANOpenHeartbeatPayload state) throws ParseException {
-        return new CANOpenFrame((short) configuration.getNodeId(), CANOpenService.HEARTBEAT, state);
-    }
-
-    public CompletableFuture<PlcWriteResponse> write(PlcWriteRequest writeRequest) {
-        CompletableFuture<PlcWriteResponse> response = new CompletableFuture<>();
-        if (writeRequest.getTagNames().size() != 1) {
-            response.completeExceptionally(new IllegalArgumentException("You can write only one tag at the time"));
-            return response;
-        }
-
-        PlcTag tag = writeRequest.getTags().get(0);
-        if (!(tag instanceof CANOpenTag)) {
-            response.completeExceptionally(new IllegalArgumentException("Only CANOpenTag instances are supported"));
-            return response;
-        }
-
-        if (tag instanceof CANOpenSDOTag) {
-            writeInternally((DefaultPlcWriteRequest) writeRequest, (CANOpenSDOTag) tag, response);
-            return response;
-        }
-        if (tag instanceof CANOpenPDOTag) {
-            writeInternally((DefaultPlcWriteRequest) writeRequest, (CANOpenPDOTag) tag, response);
-            return response;
-        }
-
-        response.completeExceptionally(new IllegalArgumentException("Only CANOpenSDOTag instances are supported"));
-        return response;
-    }
-
-    private void writeInternally(DefaultPlcWriteRequest writeRequest, CANOpenSDOTag tag, CompletableFuture<PlcWriteResponse> response) {
-        final RequestTransactionManager.RequestTransaction transaction = tm.startRequest();
-
-        String tagName = writeRequest.getTagNames().iterator().next();
-
-        CompletableFuture<PlcResponseCode> callback = new CompletableFuture<>();
-        callback.whenComplete((code, error) -> {
-            if (error != null) {
-                if (error instanceof CANOpenAbortException) {
-                    response.complete(new DefaultPlcWriteResponse(writeRequest, Collections.singletonMap(tagName, PlcResponseCode.REMOTE_ERROR)));
-                } else {
-                    response.complete(new DefaultPlcWriteResponse(writeRequest, Collections.singletonMap(tagName, PlcResponseCode.INTERNAL_ERROR)));
-                }
-                transaction.endRequest();
-                return;
-            }
-            response.complete(new DefaultPlcWriteResponse(writeRequest, Collections.singletonMap(tagName, code)));
-            transaction.endRequest();
-        });
-
-        PlcValue writeValue = writeRequest.getPlcValues().get(0);
-        SDODownloadConversation download = new SDODownloadConversation(conversation, tag.getNodeId(), tag.getAnswerNodeId(),
-            new IndexAddress(tag.getIndex(), tag.getSubIndex()), writeValue, tag.getCanOpenDataType());
-        transaction.submit(() -> download.execute(callback));
-    }
-
-    private void writeInternally(DefaultPlcWriteRequest writeRequest, CANOpenPDOTag tag, CompletableFuture<PlcWriteResponse> response) {
-        PlcValue writeValue = writeRequest.getPlcValues().get(0);
-
-        try {
-            String tagName = writeRequest.getTagNames().iterator().next();
-
-            WriteBufferByteBased writeBuffer = new WriteBufferByteBased(DataItem.getLengthInBytes(writeValue, tag.getCanOpenDataType(), writeValue.getLength()), ByteOrder.LITTLE_ENDIAN);
-            DataItem.staticSerialize(writeBuffer, writeValue, tag.getCanOpenDataType(), writeValue.getLength(), ByteOrder.LITTLE_ENDIAN);
-            final CANOpenPDOPayload payload = new CANOpenPDOPayload(new CANOpenPDO(writeBuffer.getBytes()));
-            conversationContext.sendToWire(new CANOpenFrame((short) tag.getNodeId(), tag.getService(), payload));
-            response.complete(new DefaultPlcWriteResponse(writeRequest, Collections.singletonMap(tagName, PlcResponseCode.OK)));
-        } catch (Exception e) {
-            response.completeExceptionally(e);
-        }
-    }
-
-    public CompletableFuture<PlcReadResponse> read(PlcReadRequest readRequest) {
-        CompletableFuture<PlcReadResponse> response = new CompletableFuture<>();
-        if (readRequest.getTagNames().size() != 1) {
-            response.completeExceptionally(new IllegalArgumentException("SDO requires single tag to be read"));
-            return response;
-        }
-
-        PlcTag tag = readRequest.getTags().get(0);
-        if (!(tag instanceof CANOpenTag)) {
-            response.completeExceptionally(new IllegalArgumentException("Only CANOpenTag instances are supported"));
-            return response;
-        }
-
-        if (!(tag instanceof CANOpenSDOTag)) {
-            response.completeExceptionally(new IllegalArgumentException("Only CANOpenSDOTag instances are supported"));
-            return response;
-        }
-
-        readInternally(readRequest, (CANOpenSDOTag) tag, response);
-        return response;
-    }
-
-    @Override
-    public CompletableFuture<PlcSubscriptionResponse> subscribe(PlcSubscriptionRequest request) {
-        DefaultPlcSubscriptionRequest rq = (DefaultPlcSubscriptionRequest) request;
-
-        Map<String, PlcResponseItem<PlcSubscriptionHandle>> answers = new LinkedHashMap<>();
-        DefaultPlcSubscriptionResponse response = new DefaultPlcSubscriptionResponse(rq, answers);
-
-        for (String key : rq.getTagNames()) {
-            DefaultPlcSubscriptionTag subscription = (DefaultPlcSubscriptionTag) rq.getTag(key);
-            if (subscription.getPlcSubscriptionType() != PlcSubscriptionType.EVENT) {
-                answers.put(key, new DefaultPlcResponseItem<>(PlcResponseCode.UNSUPPORTED, null));
-            } else if ((subscription.getTag() instanceof CANOpenPDOTag)) {
-                answers.put(key, new DefaultPlcResponseItem<>(PlcResponseCode.OK,
-                    new CANOpenSubscriptionHandle(this, key, (CANOpenPDOTag) subscription.getTag())
-                ));
-            } else if ((subscription.getTag() instanceof CANOpenNMTTag)) {
-                answers.put(key, new DefaultPlcResponseItem<>(PlcResponseCode.OK,
-                    new CANOpenSubscriptionHandle(this, key, (CANOpenNMTTag) subscription.getTag())
-                ));
-            } else if ((subscription.getTag() instanceof CANOpenHeartbeatTag)) {
-                answers.put(key, new DefaultPlcResponseItem<>(PlcResponseCode.OK,
-                    new CANOpenSubscriptionHandle(this, key, (CANOpenHeartbeatTag) subscription.getTag())
-                ));
-            } else {
-                answers.put(key, new DefaultPlcResponseItem<>(PlcResponseCode.INVALID_ADDRESS, null));
-            }
-        }
-
-        return CompletableFuture.completedFuture(response);
-    }
-
-    @Override
-    public CompletableFuture<PlcUnsubscriptionResponse> unsubscribe(PlcUnsubscriptionRequest request) {
-        List<PlcSubscriptionHandle> handles = request.getSubscriptionHandles();
-
-        for (Map.Entry<DefaultPlcConsumerRegistration, Consumer<PlcSubscriptionEvent>> entry : consumers.entrySet()) {
-            entry.getKey().getSubscriptionHandles().removeAll(handles);
-        }
-
-        return CompletableFuture.completedFuture(new DefaultPlcUnsubscriptionResponse(request));
-    }
-
-    private void readInternally(PlcReadRequest readRequest, CANOpenSDOTag tag, CompletableFuture<PlcReadResponse> response) {
-        String tagName = readRequest.getTagNames().iterator().next();
-
-        final RequestTransactionManager.RequestTransaction transaction = tm.startRequest();
-        CompletableFuture<PlcValue> callback = new CompletableFuture<>();
-        callback.whenComplete((value, error) -> {
-            if (error != null) {
-                Map<String, PlcResponseItem<PlcValue>> tags = new HashMap<>();
-                if (error instanceof CANOpenAbortException) {
-                    tags.put(tagName, new DefaultPlcResponseItem<>(PlcResponseCode.REMOTE_ERROR, new PlcLINT(((CANOpenAbortException) error).getAbortCode())));
-                } else {
-                    tags.put(tagName, new DefaultPlcResponseItem<>(PlcResponseCode.REMOTE_ERROR, null));
-                }
-                response.complete(new DefaultPlcReadResponse(readRequest, tags));
-                transaction.endRequest();
-
-                return;
-            }
-
-            Map<String, PlcResponseItem<PlcValue>> tags = new HashMap<>();
-            tags.put(tagName, new DefaultPlcResponseItem<>(PlcResponseCode.OK, value));
-            response.complete(new DefaultPlcReadResponse(readRequest, tags));
-            transaction.endRequest();
-        });
-
-        SDOUploadConversation upload = new SDOUploadConversation(conversation, tag.getNodeId(), tag.getAnswerNodeId(), new IndexAddress(tag.getIndex(), tag.getSubIndex()), tag.getCanOpenDataType());
-        transaction.submit(() -> upload.execute(callback));
-    }
-
-    @Override
-    public void decode(ConversationContext<CANOpenFrame> context, CANOpenFrame msg) throws Exception {
-        int nodeId = msg.getNodeId();
-        CANOpenService service = msg.getService();
-        CANOpenPayload payload = msg.getPayload();
-
-        if (service != null && nodeId != this.configuration.getNodeId()) {
-            if (service.getPdo() && payload instanceof CANOpenPDOPayload) {
-                publishEvent(service, nodeId, payload);
-            } else if (service == CANOpenService.HEARTBEAT && payload instanceof CANOpenHeartbeatPayload) {
-                publishEvent(service, nodeId, payload);
-            } else {
-                logger.debug("Decoded CANOpen {} from {}, message {}", service, nodeId, payload);
-            }
-        }
-    }
-
-    private void publishEvent(CANOpenService service, int nodeId, CANOpenPayload payload) {
-        DefaultPlcSubscriptionHandle dispatchedHandle = null;
-        for (Map.Entry<DefaultPlcConsumerRegistration, Consumer<PlcSubscriptionEvent>> entry : consumers.entrySet()) {
-            DefaultPlcConsumerRegistration registration = entry.getKey();
-            Consumer<PlcSubscriptionEvent> consumer = entry.getValue();
-
-            for (PlcSubscriptionHandle handler : registration.getSubscriptionHandles()) {
-                CANOpenSubscriptionHandle handle = (CANOpenSubscriptionHandle) handler;
-                if (payload instanceof CANOpenPDOPayload) {
-
-                    if (handle.matches(service, nodeId)) {
-                        logger.trace("Dispatching notification {} for node {} to {}", service, nodeId, handle);
-                        dispatchedHandle = handle;
-
-                        CANOpenPDOTag tag = (CANOpenPDOTag) handle.getTag();
-                        byte[] data = ((CANOpenPDOPayload) payload).getPdo().getData();
-                        try {
-                            PlcValue value = DataItem.staticParse(new ReadBufferByteBased(data, ByteOrder.LITTLE_ENDIAN), tag.getCanOpenDataType(), data.length);
-                            DefaultPlcSubscriptionEvent event = new DefaultPlcSubscriptionEvent(
-                                Instant.now(),
-                                Collections.singletonMap(
-                                    handle.getName(),
-                                    new DefaultPlcResponseItem<>(PlcResponseCode.OK, value)
-                                )
-                            );
-                            consumer.accept(event);
-                        } catch (ParseException e) {
-                            logger.warn("Could not parse data to desired type: {}", tag.getCanOpenDataType(), e);
-                            DefaultPlcSubscriptionEvent event = new DefaultPlcSubscriptionEvent(
-                                Instant.now(),
-                                Collections.singletonMap(
-                                    handle.getName(),
-                                    new DefaultPlcResponseItem<>(PlcResponseCode.INVALID_DATA, new PlcNull())
-                                )
-                            );
-                            consumer.accept(event);
-                        }
-                    }
-                } else if (payload instanceof CANOpenHeartbeatPayload) {
-                    if (handle.matches(service, nodeId)) {
-                        logger.trace("Dispatching notification {} for node {} to {}", service, nodeId, handle);
-                        dispatchedHandle = handle;
-
-                        final NMTState state = ((CANOpenHeartbeatPayload) payload).getState();
-                        Map<String, PlcValue> tags = new HashMap<>();
-                        tags.put("state", new PlcUSINT(state.getValue()));
-                        tags.put("node", new PlcUSINT(nodeId));
-                        PlcStruct struct = new PlcStruct(tags);
-                        DefaultPlcSubscriptionEvent event = new DefaultPlcSubscriptionEvent(
-                            Instant.now(),
-                            Collections.singletonMap(
-                                handle.getName(),
-                                new DefaultPlcResponseItem<>(PlcResponseCode.OK, struct)
-                            )
-                        );
-                        consumer.accept(event);
-                    }
-                } else if (payload instanceof CANOpenNetworkPayload) {
-                    if (handle.matches(service, nodeId)) {
-                        logger.trace("Dispatching notification {} for node {} to {}", service, nodeId, handle);
-                        dispatchedHandle = handle;
-
-                        final NMTStateRequest state = ((CANOpenNetworkPayload) payload).getRequest();
-                        Map<String, PlcValue> tags = new HashMap<>();
-                        tags.put("state", new PlcUSINT(state.getValue()));
-                        tags.put("node", new PlcUSINT(nodeId));
-                        PlcStruct struct = new PlcStruct(tags);
-                        DefaultPlcSubscriptionEvent event = new DefaultPlcSubscriptionEvent(
-                            Instant.now(),
-                            Collections.singletonMap(
-                                handle.getName(),
-                                new DefaultPlcResponseItem<>(PlcResponseCode.OK, struct)
-                            )
-                        );
-                        consumer.accept(event);
-                    }
-                }
-            }
-        }
-
-        if (dispatchedHandle == null) {
-            logger.trace("Could not find subscription matching {} and node {}", service, nodeId);
-        }
-    }
-
-    @Override
-    public PlcConsumerRegistration register(Consumer<PlcSubscriptionEvent> consumer, Collection<PlcSubscriptionHandle> handles) {
-        final DefaultPlcConsumerRegistration consumerRegistration = new DefaultPlcConsumerRegistration(this, consumer, handles.toArray(new DefaultPlcSubscriptionHandle[0]));
-        consumers.put(consumerRegistration, consumer);
-        return consumerRegistration;
-    }
-
-    @Override
-    public void unregister(PlcConsumerRegistration registration) {
-        consumers.remove(registration);
-    }
-
-    @Override
-    public void onDisconnect(ConversationContext<CANOpenFrame> context) {
-        if (this.heartbeat != null) {
-            this.heartbeat.cancel();
-            this.heartbeat = null;
-        }
-    }
-
-}
diff --git a/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/readwrite/utils/StaticHelper.java b/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/readwrite/utils/StaticHelper.java
index 4dab9e50d6..c502c0f08a 100644
--- a/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/readwrite/utils/StaticHelper.java
+++ b/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/readwrite/utils/StaticHelper.java
@@ -18,46 +18,23 @@
  */
 package org.apache.plc4x.java.canopen.readwrite.utils;
 
-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.CANOpenService;
-import org.apache.plc4x.java.spi.codegen.WithOption;
-import org.apache.plc4x.java.spi.generation.ParseException;
-import org.apache.plc4x.java.spi.generation.ReadBuffer;
-import org.apache.plc4x.java.spi.generation.SerializationException;
-import org.apache.plc4x.java.spi.generation.WriteBuffer;
-
-import static org.apache.plc4x.java.spi.generation.StaticHelper.COUNT;
 
 public class StaticHelper {
 
-    public static CANOpenService serviceId(short identifier) {
-        return CANOpenService.enumForValue((byte) (identifier >> 7));
-    }
-
-    public static int uploadPadding(SDOSegmentUploadResponse payload) {
-        return 7 - payload.getData().length;
-    }
-
+    /**
+     * Helper invoked by generated SDO upload/download initiate code to compute the {@code size} field.
+     *
+     * <p>For an expedited+indicated transfer carrying ≤4 bytes of inline data, {@code size}
+     * is the number of <em>unused</em> bytes (i.e. {@code 4 - data.length}). For all other
+     * payload shapes the field is zero.</p>
+     */
     public static int count(boolean expedited, boolean indicated, SDOInitiateUploadResponsePayload payload) {
-        return expedited && indicated && payload instanceof SDOInitiateExpeditedUploadResponse ? 4 - COUNT(((SDOInitiateExpeditedUploadResponse) payload).getData()) : 0;
-    }
-
-    public static void writeFunction(WriteBuffer io, short identifier) {
-        // NOOP - a placeholder to let mspec compile
-    }
-
-    public static Object parseString(ReadBuffer io, int length, String charset) throws ParseException {
-        return io.readString(8 * length, WithOption.WithEncoding(charset));
+        if (expedited && indicated && payload instanceof SDOInitiateExpeditedUploadResponse) {
+            return 4 - ((SDOInitiateExpeditedUploadResponse) payload).getData().length;
+        }
+        return 0;
     }
 
-    public static void serializeString(WriteBuffer io, PlcValue value, String charset) throws SerializationException {
-        io.writeString(8, value.getString(), WithOption.WithEncoding(charset));
-    }
-
-    public static byte[] parseByteArray(ReadBuffer io, Integer length) {
-        return new byte[0];
-    }
 }
diff --git a/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/tag/CANOpenHeartbeatTag.java b/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/tag/CANOpenHeartbeatTag.java
index 1a3ff4d060..6c53ff1ca0 100644
--- a/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/tag/CANOpenHeartbeatTag.java
+++ b/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/tag/CANOpenHeartbeatTag.java
@@ -22,11 +22,7 @@ import org.apache.plc4x.java.api.exceptions.PlcInvalidTagException;
 import org.apache.plc4x.java.api.model.ArrayInfo;
 import org.apache.plc4x.java.api.types.PlcValueType;
 import org.apache.plc4x.java.canopen.readwrite.CANOpenService;
-import org.apache.plc4x.java.spi.codegen.WithOption;
-import org.apache.plc4x.java.spi.generation.SerializationException;
-import org.apache.plc4x.java.spi.generation.WriteBuffer;
 
-import java.nio.charset.StandardCharsets;
 import java.util.Collections;
 import java.util.List;
 import java.util.regex.Matcher;
@@ -84,16 +80,4 @@ public class CANOpenHeartbeatTag extends CANOpenTag implements CANOpenSubscripti
         return new CANOpenHeartbeatTag(nodeId);
     }
 
-    @Override
-    public void serialize(WriteBuffer writeBuffer) throws SerializationException {
-        writeBuffer.pushContext(getClass().getSimpleName());
-
-        String serviceName = getService().name();
-        writeBuffer.writeString("service",
-            serviceName.getBytes(StandardCharsets.UTF_8).length * 8,
-            serviceName, WithOption.WithEncoding("UTF8"));
-        writeBuffer.writeInt("node", 64, getNodeId());
-        
-        writeBuffer.popContext(getClass().getSimpleName());
-    }
 }
diff --git a/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/tag/CANOpenNMTTag.java b/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/tag/CANOpenNMTTag.java
index 698060eafa..d4af9b2043 100644
--- a/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/tag/CANOpenNMTTag.java
+++ b/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/tag/CANOpenNMTTag.java
@@ -22,11 +22,7 @@ import org.apache.plc4x.java.api.exceptions.PlcInvalidTagException;
 import org.apache.plc4x.java.api.model.ArrayInfo;
 import org.apache.plc4x.java.api.types.PlcValueType;
 import org.apache.plc4x.java.canopen.readwrite.CANOpenService;
-import org.apache.plc4x.java.spi.codegen.WithOption;
-import org.apache.plc4x.java.spi.generation.SerializationException;
-import org.apache.plc4x.java.spi.generation.WriteBuffer;
 
-import java.nio.charset.StandardCharsets;
 import java.util.Collections;
 import java.util.List;
 import java.util.regex.Matcher;
@@ -85,16 +81,4 @@ public class CANOpenNMTTag extends CANOpenTag implements CANOpenSubscriptionTag
     }
 
 
-    @Override
-    public void serialize(WriteBuffer writeBuffer) throws SerializationException {
-        writeBuffer.pushContext(getClass().getSimpleName());
-
-        String serviceName = getService().name();
-        writeBuffer.writeString("service",
-            serviceName.getBytes(StandardCharsets.UTF_8).length * 8,
-            serviceName, WithOption.WithEncoding("UTF8"));
-        writeBuffer.writeInt("node",64, getNodeId());
-
-        writeBuffer.popContext(getClass().getSimpleName());
-    }
 }
diff --git a/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/tag/CANOpenPDOTag.java b/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/tag/CANOpenPDOTag.java
index 985ff884c7..b521040613 100644
--- a/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/tag/CANOpenPDOTag.java
+++ b/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/tag/CANOpenPDOTag.java
@@ -23,11 +23,7 @@ import org.apache.plc4x.java.api.model.ArrayInfo;
 import org.apache.plc4x.java.api.types.PlcValueType;
 import org.apache.plc4x.java.canopen.readwrite.CANOpenDataType;
 import org.apache.plc4x.java.canopen.readwrite.CANOpenService;
-import org.apache.plc4x.java.spi.codegen.WithOption;
-import org.apache.plc4x.java.spi.generation.SerializationException;
-import org.apache.plc4x.java.spi.generation.WriteBuffer;
 
-import java.nio.charset.StandardCharsets;
 import java.util.Collections;
 import java.util.List;
 import java.util.regex.Matcher;
@@ -101,20 +97,4 @@ public class CANOpenPDOTag extends CANOpenTag implements CANOpenSubscriptionTag
         return false;
     }
 
-    @Override
-    public void serialize(WriteBuffer writeBuffer) throws SerializationException {
-        writeBuffer.pushContext(getClass().getSimpleName());
-
-        String serviceName = getService().name();
-        writeBuffer.writeString("service",
-            serviceName.getBytes(StandardCharsets.UTF_8).length * 8,
-            serviceName, WithOption.WithEncoding("UTF8"));
-        writeBuffer.writeInt("node",64, getNodeId());
-        String dataTypeName = getCanOpenDataType().name();
-        writeBuffer.writeString("dataType",
-            dataTypeName.getBytes(StandardCharsets.UTF_8).length * 8,
-            dataTypeName, WithOption.WithEncoding("UTF8"));
-
-        writeBuffer.popContext(getClass().getSimpleName());
-    }
 }
diff --git a/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/tag/CANOpenSDOTag.java b/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/tag/CANOpenSDOTag.java
index c8af486304..a7b11a02e3 100644
--- a/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/tag/CANOpenSDOTag.java
+++ b/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/tag/CANOpenSDOTag.java
@@ -22,11 +22,7 @@ import org.apache.plc4x.java.api.exceptions.PlcInvalidTagException;
 import org.apache.plc4x.java.api.model.ArrayInfo;
 import org.apache.plc4x.java.api.types.PlcValueType;
 import org.apache.plc4x.java.canopen.readwrite.CANOpenDataType;
-import org.apache.plc4x.java.spi.codegen.WithOption;
-import org.apache.plc4x.java.spi.generation.SerializationException;
-import org.apache.plc4x.java.spi.generation.WriteBuffer;
 
-import java.nio.charset.StandardCharsets;
 import java.util.Collections;
 import java.util.List;
 import java.util.regex.Matcher;
@@ -133,19 +129,4 @@ public class CANOpenSDOTag extends CANOpenTag {
         return Short.parseShort(dec);
     }
 
-    @Override
-    public void serialize(WriteBuffer writeBuffer) throws SerializationException {
-        writeBuffer.pushContext(getClass().getSimpleName());
-
-        writeBuffer.writeInt("node",64, getNodeId());
-        writeBuffer.writeInt("index",64, getIndex());
-        writeBuffer.writeInt("subIndex",64, getSubIndex());
-        String dataTypeName = getCanOpenDataType().name();
-        writeBuffer.writeString("dataType",
-            dataTypeName.getBytes(StandardCharsets.UTF_8).length * 8,
-            dataTypeName, WithOption.WithEncoding("UTF8"));
-
-        writeBuffer.popContext(getClass().getSimpleName());
-    }
-
 }
diff --git a/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/tag/CANOpenTag.java b/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/tag/CANOpenTag.java
index f98e471b6d..39dcfeb82f 100644
--- a/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/tag/CANOpenTag.java
+++ b/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/tag/CANOpenTag.java
@@ -20,14 +20,13 @@ package org.apache.plc4x.java.canopen.tag;
 
 import org.apache.plc4x.java.api.exceptions.PlcInvalidTagException;
 import org.apache.plc4x.java.api.model.PlcTag;
-import org.apache.plc4x.java.spi.utils.Serializable;
 
 import java.util.regex.Pattern;
 
 /**
  * Generic tag type which defines node address and address pattern (index/subindex).
  */
-public abstract class CANOpenTag implements PlcTag, Serializable {
+public abstract class CANOpenTag implements PlcTag {
 
     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+)");
diff --git a/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/tag/CANOpenTagHandler.java b/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/tag/CANOpenTagHandler.java
index fc0025233b..f728d2e727 100644
--- a/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/tag/CANOpenTagHandler.java
+++ b/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/tag/CANOpenTagHandler.java
@@ -19,14 +19,14 @@
 package org.apache.plc4x.java.canopen.tag;
 
 import org.apache.plc4x.java.api.exceptions.PlcInvalidTagException;
-import org.apache.plc4x.java.api.model.PlcTag;
 import org.apache.plc4x.java.api.model.PlcQuery;
-import org.apache.plc4x.java.spi.connection.PlcTagHandler;
+import org.apache.plc4x.java.api.model.PlcTag;
+import org.apache.plc4x.java.spi.drivers.tags.PlcTagHandler;
 
 public class CANOpenTagHandler implements PlcTagHandler {
 
     @Override
-    public PlcTag parseTag(String tagAddress) throws PlcInvalidTagException {
+    public PlcTag parseTag(String tagAddress) {
         return CANOpenTag.of(tagAddress);
     }
 
diff --git a/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/transport/CANOpenFrameDataHandler.java b/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/transport/CANOpenFrameDataHandler.java
deleted file mode 100644
index ec52a771c4..0000000000
--- a/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/transport/CANOpenFrameDataHandler.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
-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
-
-  https://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.canopen.transport;
-
-import java.util.function.Supplier;
-import org.apache.plc4x.java.api.exceptions.PlcRuntimeException;
-import org.apache.plc4x.java.canopen.readwrite.utils.StaticHelper;
-import org.apache.plc4x.java.canopen.readwrite.CANOpenFrame;
-import org.apache.plc4x.java.canopen.readwrite.CANOpenPayload;
-import org.apache.plc4x.java.canopen.readwrite.CANOpenService;
-import org.apache.plc4x.java.spi.generation.ByteOrder;
-import org.apache.plc4x.java.spi.generation.Message;
-import org.apache.plc4x.java.spi.generation.SerializationException;
-import org.apache.plc4x.java.spi.generation.WriteBufferByteBased;
-import org.apache.plc4x.java.transport.can.CANFrameBuilder;
-import org.apache.plc4x.java.transport.can.CANTransport.FrameHandler;
-import org.apache.plc4x.java.transport.can.FrameData;
-
-/**
- * Dedicated frame handler instance which can create {@link CANOpenFrame} from received {@link FrameData} and dump it back
- * via passed {@link CANFrameBuilder} back to the wire.
- */
-public class CANOpenFrameDataHandler implements FrameHandler<Message, CANOpenFrame> {
-
-    private final Supplier<CANFrameBuilder<Message>> builder;
-
-    public CANOpenFrameDataHandler(Supplier<CANFrameBuilder<Message>> builder) {
-        this.builder = builder;
-    }
-
-    @Override
-    public CANOpenFrame fromCAN(FrameData frame) {
-        CANOpenService service = StaticHelper.serviceId((short) frame.getNodeId());
-        int nodeId = Math.abs(service.getMin() - frame.getNodeId());
-        return new CANOpenFrame((short) nodeId, service, frame.read(io -> CANOpenPayload.staticParse(io, service)));
-    }
-
-    @Override
-    public Message toCAN(CANOpenFrame frame) {
-        try {
-            CANOpenPayload payload = frame.getPayload();
-            WriteBufferByteBased buffer = new WriteBufferByteBased(payload.getLengthInBytes(), ByteOrder.LITTLE_ENDIAN);
-            payload.serialize(buffer);
-            return builder.get().withId(frame.getService().getMin() + frame.getNodeId())
-                .withData(buffer.getBytes())
-                .create();
-        } catch (SerializationException e) {
-            throw new PlcRuntimeException(e);
-        }
-    }
-
-}
diff --git a/plc4j/drivers/canopen/src/main/resources/META-INF/services/org.apache.plc4x.java.api.PlcDriver b/plc4j/drivers/canopen/src/main/resources/META-INF/services/org.apache.plc4x.java.api.PlcDriver
index 8392688cce..65ca3d97da 100644
--- a/plc4j/drivers/canopen/src/main/resources/META-INF/services/org.apache.plc4x.java.api.PlcDriver
+++ b/plc4j/drivers/canopen/src/main/resources/META-INF/services/org.apache.plc4x.java.api.PlcDriver
@@ -16,4 +16,4 @@
 # specific language governing permissions and limitations
 # under the License.
 #
-org.apache.plc4x.java.canopen.CANOpenPlcDriver
+org.apache.plc4x.java.canopen.CANOpenDriver
diff --git a/plc4j/drivers/canopen/src/test/java/org/apache/plc4x/java/canopen/CANOpenDriverSDOIT.java b/plc4j/drivers/canopen/src/test/java/org/apache/plc4x/java/canopen/CANOpenDriverSDOIT.java
deleted file mode 100644
index 764486be76..0000000000
--- a/plc4j/drivers/canopen/src/test/java/org/apache/plc4x/java/canopen/CANOpenDriverSDOIT.java
+++ /dev/null
@@ -1,31 +0,0 @@
-/*
- * 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
- *
- *   https://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.canopen;
-
-import org.apache.plc4x.test.driver.DriverTestsuiteRunner;
-import org.junit.jupiter.api.Disabled;
-
-@Disabled("Fix this")
-class CANOpenDriverSDOIT extends DriverTestsuiteRunner {
-
-    public CANOpenDriverSDOIT() {
-        super("/protocols/canopen/CANOpenDriverSDOIT.xml", true);
-    }
-
-}
diff --git a/plc4j/drivers/canopen/src/test/java/org/apache/plc4x/java/canopen/CANOpenPayloadTest.java b/plc4j/drivers/canopen/src/test/java/org/apache/plc4x/java/canopen/CANOpenPayloadTest.java
deleted file mode 100644
index eecf9ba38b..0000000000
--- a/plc4j/drivers/canopen/src/test/java/org/apache/plc4x/java/canopen/CANOpenPayloadTest.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * 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
- *
- *   https://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.canopen;
-
-import org.apache.plc4x.test.parserserializer.ParserSerializerTestsuiteRunner;
-
-public class CANOpenPayloadTest extends ParserSerializerTestsuiteRunner {
-
-    public CANOpenPayloadTest() {
-        super("/protocols/canopen/CANOpenPayloadTestSuite.xml");
-    }
-
-}
diff --git a/plc4j/drivers/canopen/src/test/java/org/apache/plc4x/java/canopen/CANOpenWiresharkTest.java b/plc4j/drivers/canopen/src/test/java/org/apache/plc4x/java/canopen/CANOpenWiresharkTest.java
deleted file mode 100644
index e88e78e970..0000000000
--- a/plc4j/drivers/canopen/src/test/java/org/apache/plc4x/java/canopen/CANOpenWiresharkTest.java
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * 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
- *
- *   https://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.canopen;
-
-import org.apache.plc4x.test.parserserializer.ParserSerializerTestsuiteRunner;
-
-public class CANOpenWiresharkTest extends ParserSerializerTestsuiteRunner {
-
-    public CANOpenWiresharkTest() {
-        super("/protocols/canopen/CANOpenWiresharkTestSuite.xml", true);
-    }
-
-}
diff --git a/plc4j/drivers/canopen/src/test/java/org/apache/plc4x/java/canopen/CANTestTransport.java b/plc4j/drivers/canopen/src/test/java/org/apache/plc4x/java/canopen/CANTestTransport.java
deleted file mode 100644
index 92286d0dbd..0000000000
--- a/plc4j/drivers/canopen/src/test/java/org/apache/plc4x/java/canopen/CANTestTransport.java
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * 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
- *
- *   https://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.canopen;
-
-import io.netty.buffer.ByteBuf;
-
-import java.util.function.Function;
-import java.util.function.ToIntFunction;
-
-import org.apache.plc4x.java.spi.configuration.PlcConnectionConfiguration;
-import org.apache.plc4x.java.canopen.readwrite.CANOpenFrame;
-import org.apache.plc4x.java.canopen.transport.CANOpenFrameDataAdapter;
-import org.apache.plc4x.java.canopen.transport.IdentityCANOpenFrameBuilder;
-import org.apache.plc4x.java.spi.generation.MessageInput;
-import org.apache.plc4x.java.transport.can.CANFrameBuilder;
-import org.apache.plc4x.java.transport.can.CANTransport;
-import org.apache.plc4x.java.transport.can.FrameData;
-import org.apache.plc4x.java.transport.test.TestTransport;
-
-public class CANTestTransport extends TestTransport implements CANTransport<CANOpenFrame> {
-    @Override
-    public ToIntFunction<ByteBuf> getEstimator() {
-        // id (1 byte), service (1 byte), data (up to 8 bytes, padded)
-        return (buffer) -> 10;
-    }
-
-    @Override
-    public Class<CANOpenFrame> getMessageType() {
-        return CANOpenFrame.class;
-    }
-
-    @Override
-    public MessageInput<CANOpenFrame> getMessageInput(PlcConnectionConfiguration configuration) {
-        return CANOpenFrame::staticParse;
-    }
-
-    @Override
-    public CANFrameBuilder<CANOpenFrame> getTransportFrameBuilder() {
-        return new IdentityCANOpenFrameBuilder();
-    }
-
-    @Override
-    public Function<CANOpenFrame, FrameData> adapter() {
-        return new CANOpenFrameDataAdapter();
-    }
-
-}
diff --git a/plc4j/drivers/canopen/src/test/java/org/apache/plc4x/java/canopen/Main.java b/plc4j/drivers/canopen/src/test/java/org/apache/plc4x/java/canopen/Main.java
deleted file mode 100644
index 7716df2a59..0000000000
--- a/plc4j/drivers/canopen/src/test/java/org/apache/plc4x/java/canopen/Main.java
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * 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
- *
- *   https://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.canopen;
-
-import org.apache.plc4x.java.DefaultPlcDriverManager;
-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.canopen.readwrite.CANOpenFrame;
-import org.apache.plc4x.java.canopen.context.CANOpenDriverContext;
-import org.apache.plc4x.java.canopen.listener.Callback;
-
-import java.util.concurrent.CompletableFuture;
-
-/**
- * Here we begin .. ;-)
- */
-public class Main {
-
-    public static void main(String[] args) throws Exception {
-        DefaultPlcDriverManager driverManager = new DefaultPlcDriverManager();
-
-        CANOpenDriverContext.CALLBACK.addCallback(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()
-            .addTagAddress("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.addTagAddress("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/plc4j/drivers/canopen/src/test/java/org/apache/plc4x/java/canopen/ManualParserTest.java b/plc4j/drivers/canopen/src/test/java/org/apache/plc4x/java/canopen/ManualParserTest.java
deleted file mode 100644
index 2942ea01cd..0000000000
--- a/plc4j/drivers/canopen/src/test/java/org/apache/plc4x/java/canopen/ManualParserTest.java
+++ /dev/null
@@ -1,115 +0,0 @@
-/*
- * 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
- *
- *   https://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.canopen;
-
-import org.apache.commons.codec.binary.Hex;
-import org.apache.plc4x.java.spi.generation.ByteOrder;
-import org.apache.plc4x.java.spi.generation.ReadBuffer;
-import org.apache.plc4x.java.spi.generation.ReadBufferByteBased;
-import org.junit.jupiter.api.Test;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
-public class ManualParserTest {
-
-    public static final int EXTENDED_FRAME_FORMAT_FLAG = 0b10000000_00000000_00000000_00000000;
-    public static final int REMOTE_TRANSMISSION_FLAG = 0b01000000_00000000_00000000_00000000;
-    public static final int ERROR_FRAME_FLAG = 0b00100000_00000000_00000000_00000000;
-
-    public static final int STANDARD_FORMAT_IDENTIFIER_MASK = 0b00000000_00000000_00000111_11111111;
-    public static final int EXTENDED_FORMAT_IDENTIFIER_MASK = 0b00011111_11111111_11111111_11111111;
-
-    // cansend 5A1#11.2233.44556677.88
-    final String STANDARD = "a1050000080000001122334455667788";
-
-    // cansend 5A1#R
-    String STANDARD_REPLY = "a1050040000000000000000000000000";
-
-    // cansend 1E6EC676#05.05.1F.26.C3
-    final String EXTENDED = "76c66e9e0500000005051f26c3000000";
-
-    @Test
-    public void readBufferTest() throws Exception {
-        ReadBuffer buffer = new ReadBufferByteBased(new byte[]{(byte) 0xA1, 0x05, 0x00, 0x00}, ByteOrder.LITTLE_ENDIAN);
-        int value = buffer.readInt(32);
-
-        assertEquals(value, 0x5A1);
-    }
-
-    @Test
-    public void standardFrameParser() throws Exception {
-        SocketCanFrameStub frame = parse(STANDARD);
-        //System.out.println(frame);
-
-        assertEquals(frame.id, 0x5A1);
-        assertEquals(frame.extended, false);
-        assertEquals(frame.remote, false);
-        assertEquals(frame.error, false);
-        assertEquals(frame.data.length, 8);
-    }
-
-    @Test
-    public void extendedFrameParser() throws Exception {
-        SocketCanFrameStub frame = parse(EXTENDED);
-        //System.out.println(frame);
-
-        assertEquals(frame.id, 0x1e6ec676);
-        assertEquals(frame.extended, true);
-        assertEquals(frame.remote, false);
-        assertEquals(frame.error, false);
-        assertEquals(frame.data.length, 5);
-    }
-
-    public static SocketCanFrameStub parse(String hex) throws Exception {
-        byte[] input = Hex.decodeHex(hex.toCharArray());
-
-        ReadBufferByteBased readBuffer = new ReadBufferByteBased(input, ByteOrder.LITTLE_ENDIAN);
-        int rawId = readBuffer.readInt(32);
-        boolean extended = (rawId & EXTENDED_FRAME_FORMAT_FLAG) != 0;
-        boolean remote = (rawId & REMOTE_TRANSMISSION_FLAG) != 0;
-        boolean error = (rawId & ERROR_FRAME_FLAG) != 0;
-        int id = extended ? (rawId & EXTENDED_FORMAT_IDENTIFIER_MASK) : (rawId & STANDARD_FORMAT_IDENTIFIER_MASK);
-        int length = readBuffer.readByte();
-        byte[] data = readBuffer.getBytes(8, 8 + length);
-
-        return new SocketCanFrameStub(
-            id, extended, remote, error, data
-        );
-    }
-
-    static class SocketCanFrameStub {
-        public final int id;
-        public final boolean extended;
-        public final boolean remote;
-        public final boolean error;
-        public final byte[] data;
-
-        public SocketCanFrameStub(int id, boolean extended, boolean remote, boolean error, byte[] data) {
-            this.id = id;
-            this.extended = extended;
-            this.remote = remote;
-            this.error = error;
-            this.data = data;
-        }
-
-        public String toString() {
-            return "CAN Frame ID=" + Integer.toHexString(id) + ", extended=" + extended + ", remote=" + remote + ", error=" + error + ", data=" + Hex.encodeHexString(data);
-        }
-    }
-}
diff --git a/plc4j/drivers/canopen/src/test/java/org/apache/plc4x/java/canopen/transport/CANOpenFrameDataAdapter.java b/plc4j/drivers/canopen/src/test/java/org/apache/plc4x/java/canopen/transport/CANOpenFrameDataAdapter.java
deleted file mode 100644
index 62f3c41bfc..0000000000
--- a/plc4j/drivers/canopen/src/test/java/org/apache/plc4x/java/canopen/transport/CANOpenFrameDataAdapter.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
-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
-
-  https://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.canopen.transport;
-
-import org.apache.plc4x.java.canopen.readwrite.CANOpenFrame;
-import org.apache.plc4x.java.spi.generation.Message;
-import org.apache.plc4x.java.spi.generation.MessageInput;
-import org.apache.plc4x.java.transport.can.FrameData;
-
-import java.util.function.Function;
-
-/**
- * Adapter from {@link CANOpenFrame} to transport friendly form of frame represented by {@link FrameData}.
- *
- * Since {@link CANOpenFrame} is generated, hence it can be serialized. Yet, to save bandwidth with
- * {@link org.apache.plc4x.java.canopen.CANTestTransport} we just do bare minimum to get it working with
- * object to object mapping.
- */
-public class CANOpenFrameDataAdapter implements Function<CANOpenFrame, FrameData> {
-
-    @Override
-    public FrameData apply(CANOpenFrame frame) {
-        return new FrameData() {
-            @Override
-            public int getNodeId() {
-                return frame.getService().getMin() + frame.getNodeId();
-            }
-
-            @Override
-            public <T extends Message> T read(MessageInput<T> input) {
-                return (T) frame.getPayload();
-            }
-
-            @Override
-            public int getDataLength() {
-                return frame.getPayload().getLengthInBytes();
-            }
-
-            @Override
-            public byte[] getData() {
-                return new byte[0];
-            }
-        };
-    }
-
-}
diff --git a/plc4j/drivers/canopen/src/test/java/org/apache/plc4x/java/canopen/transport/IdentityCANOpenFrameBuilder.java b/plc4j/drivers/canopen/src/test/java/org/apache/plc4x/java/canopen/transport/IdentityCANOpenFrameBuilder.java
deleted file mode 100644
index 2f967d48ca..0000000000
--- a/plc4j/drivers/canopen/src/test/java/org/apache/plc4x/java/canopen/transport/IdentityCANOpenFrameBuilder.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
-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
-
-  https://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.canopen.transport;
-
-import org.apache.plc4x.java.api.exceptions.PlcRuntimeException;
-import org.apache.plc4x.java.canopen.readwrite.CANOpenPayload;
-import org.apache.plc4x.java.canopen.readwrite.utils.StaticHelper;
-import org.apache.plc4x.java.canopen.readwrite.CANOpenFrame;
-import org.apache.plc4x.java.canopen.readwrite.CANOpenService;
-import org.apache.plc4x.java.spi.generation.ByteOrder;
-import org.apache.plc4x.java.spi.generation.ParseException;
-import org.apache.plc4x.java.spi.generation.ReadBufferByteBased;
-import org.apache.plc4x.java.transport.can.CANFrameBuilder;
-
-/**
- * A frame builder which simply re-constructs {@link CANOpenFrame} from its tags.
- *
- * This type is used to declare wire format of data on CAN bus.
- */
-public class IdentityCANOpenFrameBuilder implements CANFrameBuilder<CANOpenFrame> {
-
-    private short nodeId;
-    private CANOpenService service;
-    private byte[] data;
-
-    @Override
-    public CANFrameBuilder<CANOpenFrame> withId(int nodeId) {
-        this.service = StaticHelper.serviceId((short) nodeId);
-        this.nodeId = (short) Math.abs(service.getMin() - nodeId);
-        return this;
-    }
-
-    @Override
-    public CANFrameBuilder<CANOpenFrame> withData(byte[] data) {
-        this.data = data;
-        return this;
-    }
-
-    @Override
-    public CANOpenFrame create() {
-        try {
-            return new CANOpenFrame(
-                nodeId, service, CANOpenPayload.staticParse(new ReadBufferByteBased(data, ByteOrder.LITTLE_ENDIAN), service)
-            );
-        } catch (ParseException e) {
-            throw new PlcRuntimeException(e);
-        }
-    }
-
-}
diff --git a/plc4j/spi/buffers/byte/src/main/java/org/apache/plc4x/java/spi/buffers/bytebased/ReadBufferByteBased.java b/plc4j/spi/buffers/byte/src/main/java/org/apache/plc4x/java/spi/buffers/bytebased/ReadBufferByteBased.java
index cafdeda878..380252533e 100644
--- a/plc4j/spi/buffers/byte/src/main/java/org/apache/plc4x/java/spi/buffers/bytebased/ReadBufferByteBased.java
+++ b/plc4j/spi/buffers/byte/src/main/java/org/apache/plc4x/java/spi/buffers/bytebased/ReadBufferByteBased.java
@@ -285,18 +285,20 @@ public class ReadBufferByteBased extends AbstractBufferByteBased implements Read
 
     @Override
     public float readFloat(int numBits, WithOption... options) throws BufferException {
-        if (numBits != 32) {
-            throw new BufferException("Float can only be read using 32 bits");
-        }
-
         Optional<Encoding> encodingOptional = getFloatEncoding(options);
-        Encoding encoding = encodingOptional.orElseThrow(() -> new BufferException("No encoding defined for signed integer values"));
-        if(encoding instanceof EncodingDefault encodingDefault) {
+        Encoding encoding = encodingOptional.orElseThrow(() -> new BufferException("No encoding defined for float values"));
+        // IEEE 754 only supports 32-bit floats; other encodings (e.g. KNXFloat,
+        // which is a 16-bit format) decide their own size constraints inside
+        // their decodeFloat implementation.
+        if (encoding instanceof EncodingDefault encodingDefault) {
+            if ("IEEE754".equals(encoding.getName()) && numBits != 32) {
+                throw new BufferException("IEEE 754 floats can only be read using 32 bits");
+            }
             ensureAvailable(numBits);
             byte[] bytes = readBits(numBits);
             bytes = getByteOrder(options).process(bytes);
             return encodingDefault.decodeFloat(numBits, bytes);
-        } else if(encoding instanceof EncodingRaw encodingRaw) {
+        } else if (encoding instanceof EncodingRaw encodingRaw) {
             return encodingRaw.decodeFloat(numBits, this);
         }
         throw new BufferException("Unsupported encoding type: " +  encoding.getClass().getName());
diff --git a/plc4j/spi/buffers/byte/src/main/java/org/apache/plc4x/java/spi/buffers/bytebased/encoding/EncodingASCII.java b/plc4j/spi/buffers/byte/src/main/java/org/apache/plc4x/java/spi/buffers/bytebased/encoding/EncodingASCII.java
index 439e150a97..86abc3735d 100644
--- a/plc4j/spi/buffers/byte/src/main/java/org/apache/plc4x/java/spi/buffers/bytebased/encoding/EncodingASCII.java
+++ b/plc4j/spi/buffers/byte/src/main/java/org/apache/plc4x/java/spi/buffers/bytebased/encoding/EncodingASCII.java
@@ -19,6 +19,7 @@
 package org.apache.plc4x.java.spi.buffers.bytebased.encoding;
 
 import org.apache.plc4x.java.spi.buffers.api.WithOption;
+import org.apache.plc4x.java.spi.buffers.api.exceptions.BufferException;
 
 import java.nio.charset.Charset;
 import java.nio.charset.StandardCharsets;
@@ -26,6 +27,13 @@ import java.nio.charset.StandardCharsets;
 /**
  * In ASCII encoding, numbers are simply represented as their ASCII-encoded string value
  * Each value must have a bit-length that must be a multiple of 8.
+ *
+ * <p>Strings in ASCII fields are right-padded with spaces ({@code 0x20}) — the
+ * Open-Protocol spec mandates this and the round-trip decode (in
+ * {@link BaseStringEncoding}) already trims trailing spaces, so this matches.
+ * The {@link BaseStringEncoding#encodeString(int, String, boolean)} default
+ * leaves a zero-padded byte array, which other ASCII users could quietly
+ * tolerate but which Open-Protocol explicitly forbids.</p>
  */
 public class EncodingASCII extends BaseStringEncoding {
 
@@ -52,4 +60,26 @@ public class EncodingASCII extends BaseStringEncoding {
         return StandardCharsets.US_ASCII;
     }
 
+    /**
+     * String values get right-padded with spaces ({@code 0x20}) rather than
+     * the default zero-pad. Numeric values still get the parent class's
+     * '0'-left-pad treatment.
+     */
+    @Override
+    public byte[] encodeString(int numBits, String value) throws BufferException {
+        byte[] result = super.encodeString(numBits, value);
+        int numBytes = numBits / 8;
+        byte[] valueBytes = value == null ? new byte[0] : value.getBytes(getCharset());
+        // The parent left the trailing slots (after the actual string bytes)
+        // zero-initialized; replace those with ASCII spaces. We only touch
+        // bytes the parent didn't populate — anything inside the string range
+        // is left untouched.
+        for (int i = valueBytes.length; i < numBytes; i++) {
+            if (result[i] == 0) {
+                result[i] = ' ';
+            }
+        }
+        return result;
+    }
+
 }
diff --git a/plc4j/transports/socketcan/pom.xml b/plc4j/transports/socketcan/pom.xml
index fc0ebcd493..f842fd2720 100644
--- a/plc4j/transports/socketcan/pom.xml
+++ b/plc4j/transports/socketcan/pom.xml
@@ -37,9 +37,6 @@
 
   <build>
     <plugins>
-      <!--
-       Generate an OSGI compatible MANIFEST file.
-      -->
       <plugin>
         <groupId>org.apache.felix</groupId>
         <artifactId>maven-bundle-plugin</artifactId>
@@ -48,7 +45,7 @@
           <instructions>
             <Bundle-SymbolicName>${project.groupId}.${project.artifactId}</Bundle-SymbolicName>
             <Bundle-Activator>org.apache.plc4x.java.osgi.TransportActivator</Bundle-Activator>
-            <Export-Service>org.apache.plc4x.java.spi.transport.Transport,org.apache.plc4x.java.transport.socketcan.SocketCANTransport</Export-Service>
+            <Export-Service>org.apache.plc4x.java.spi.transports.api.Transport,org.apache.plc4x.java.transport.socketcan.SocketCANTransport</Export-Service>
           </instructions>
         </configuration>
       </plugin>
@@ -58,31 +55,41 @@
   <dependencies>
     <dependency>
       <groupId>org.apache.plc4x</groupId>
-      <artifactId>plc4j-api</artifactId>
+      <artifactId>plc4j-spi-buffers-api</artifactId>
       <version>0.14.0-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>org.apache.plc4x</groupId>
-      <artifactId>plc4j-spi</artifactId>
+      <artifactId>plc4j-spi-buffers-byte</artifactId>
       <version>0.14.0-SNAPSHOT</version>
     </dependency>
     <dependency>
       <groupId>org.apache.plc4x</groupId>
-      <artifactId>plc4j-transports-can</artifactId>
+      <artifactId>plc4j-spi-config</artifactId>
       <version>0.14.0-SNAPSHOT</version>
     </dependency>
     <dependency>
-      <groupId>tel.schich</groupId>
-      <artifactId>javacan-core</artifactId>
+      <groupId>org.apache.plc4x</groupId>
+      <artifactId>plc4j-spi-fields</artifactId>
+      <version>0.14.0-SNAPSHOT</version>
     </dependency>
-
     <dependency>
-      <groupId>io.netty</groupId>
-      <artifactId>netty-transport</artifactId>
+      <groupId>org.apache.plc4x</groupId>
+      <artifactId>plc4j-transports-api</artifactId>
+      <version>0.14.0-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.plc4x</groupId>
+      <artifactId>plc4j-utils-audit-log-api</artifactId>
+      <version>0.14.0-SNAPSHOT</version>
+    </dependency>
+    <dependency>
+      <groupId>tel.schich</groupId>
+      <artifactId>javacan-core</artifactId>
     </dependency>
     <dependency>
-      <groupId>io.netty</groupId>
-      <artifactId>netty-buffer</artifactId>
+      <groupId>org.slf4j</groupId>
+      <artifactId>slf4j-api</artifactId>
     </dependency>
   </dependencies>
 
@@ -123,7 +130,7 @@
 
         <dependency>
           <groupId>org.apache.plc4x</groupId>
-          <artifactId>plc4x-code-generation-language-java</artifactId>
+          <artifactId>plc4x-code-generation-language-java-jp</artifactId>
           <version>0.14.0-SNAPSHOT</version>
           <scope>provided</scope>
         </dependency>
diff --git a/plc4j/transports/socketcan/src/main/generated/org/apache/plc4x/java/socketcan/readwrite/SocketCANFrame.java b/plc4j/transports/socketcan/src/main/generated/org/apache/plc4x/java/socketcan/readwrite/SocketCANFrame.java
index c40f9f5611..1e9b1a7138 100644
--- a/plc4j/transports/socketcan/src/main/generated/org/apache/plc4x/java/socketcan/readwrite/SocketCANFrame.java
+++ b/plc4j/transports/socketcan/src/main/generated/org/apache/plc4x/java/socketcan/readwrite/SocketCANFrame.java
@@ -1,157 +1,170 @@
-/*
- * 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
- *
- *   https://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.
- */
+// 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
+//
+//   https://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.socketcan.readwrite;
 
-import static org.apache.plc4x.java.spi.codegen.fields.FieldReaderFactory.*;
-import static org.apache.plc4x.java.spi.codegen.fields.FieldWriterFactory.*;
-import static org.apache.plc4x.java.spi.codegen.io.DataReaderFactory.*;
-import static org.apache.plc4x.java.spi.codegen.io.DataWriterFactory.*;
-import static org.apache.plc4x.java.spi.generation.StaticHelper.*;
-
-import java.time.*;
-import java.util.*;
-import org.apache.plc4x.java.api.exceptions.*;
-import org.apache.plc4x.java.api.value.*;
-import org.apache.plc4x.java.spi.codegen.*;
-import org.apache.plc4x.java.spi.codegen.fields.*;
-import org.apache.plc4x.java.spi.codegen.io.*;
-import org.apache.plc4x.java.spi.generation.*;
-
-// Code generated by code-generation. DO NOT EDIT.
-
+import org.apache.plc4x.java.socketcan.readwrite.utils.StaticHelper;
+import org.apache.plc4x.java.spi.buffers.api.Message;
+import org.apache.plc4x.java.spi.buffers.api.ReadBuffer;
+import org.apache.plc4x.java.spi.buffers.api.WithOption;
+import org.apache.plc4x.java.spi.buffers.api.WriteBuffer;
+import org.apache.plc4x.java.spi.buffers.api.exceptions.BufferException;
+import org.apache.plc4x.java.spi.buffers.bytebased.WithByteBasedOption;
+import org.apache.plc4x.java.spi.fields.data.reader.DataReaderFactory;
+import org.apache.plc4x.java.spi.fields.data.writer.DataWriterFactory;
+import org.apache.plc4x.java.spi.fields.fields.reader.FieldReaderFactory;
+import org.apache.plc4x.java.spi.fields.fields.writer.FieldWriterFactory;
+import org.apache.plc4x.java.spi.fields.utils.ThreadLocalHelper;
+
+/**
+ * Code generated by code-generation. DO NOT EDIT.
+ */
 public class SocketCANFrame implements Message {
-
-  // Properties.
   protected final int rawId;
 
-  /** padding 2 */
   protected final byte[] data;
 
-  public SocketCANFrame(int rawId, byte[] data) {
-    super();
+  public SocketCANFrame(Integer rawId, byte[] data) {
     this.rawId = rawId;
     this.data = data;
   }
 
+  /**
+   * Property field rawId
+   */
   public int getRawId() {
     return rawId;
   }
 
-  /** padding 2 */
+  /**
+   * Property field data
+   */
   public byte[] getData() {
     return data;
   }
 
+  /**
+   * Virtual field identifier
+   */
   public int getIdentifier() {
-    return (int)
-        (org.apache.plc4x.java.socketcan.readwrite.utils.StaticHelper.readIdentifier(getRawId()));
+    return (int) (StaticHelper.readIdentifier(rawId));
   }
 
+  /**
+   * Virtual field extended
+   */
   public boolean getExtended() {
-    return (boolean)
-        (org.apache.plc4x.java.socketcan.readwrite.utils.StaticHelper.isExtended(getRawId()));
+    return (boolean) (StaticHelper.isExtended(rawId));
   }
 
+  /**
+   * Virtual field remote
+   */
   public boolean getRemote() {
-    return (boolean)
-        (org.apache.plc4x.java.socketcan.readwrite.utils.StaticHelper.isRemote(getRawId()));
+    return (boolean) (StaticHelper.isRemote(rawId));
   }
 
+  /**
+   * Virtual field error
+   */
   public boolean getError() {
-    return (boolean)
-        (org.apache.plc4x.java.socketcan.readwrite.utils.StaticHelper.isError(getRawId()));
+    return (boolean) (StaticHelper.isError(rawId));
   }
 
-  public void serialize(WriteBuffer writeBuffer) throws SerializationException {
-    PositionAware positionAware = writeBuffer;
+  public static SocketCANFrame staticParse(ReadBuffer readBuffer) throws BufferException {
+    readBuffer.pushContext(WithOption.WithName("SocketCANFrame"), WithOption.WithFloatEncoding("IEEE754"), WithOption.WithSignedIntegerEncoding("twos-complement"), WithOption.WithUnsignedIntegerEncoding("unsigned-binary"), WithByteBasedOption.WithByteOrder("LITTLE_ENDIAN"), WithOption.WithStringEncoding("UTF8"));
+    int startPos = readBuffer.getPositionInBits();
     boolean _lastItem = ThreadLocalHelper.lastItemThreadLocal.get();
-    writeBuffer.pushContext("SocketCANFrame");
-
-    // Simple Field (rawId)
-    writeSimpleField(
-        "rawId",
-        rawId,
-        writeSignedInt(writeBuffer, 32),
-        WithOption.WithByteOrder(ByteOrder.LITTLE_ENDIAN));
-
-    // Virtual field (doesn't serialize anything, just makes the value available)
-    int identifier = getIdentifier();
-    writeBuffer.writeVirtual("identifier", identifier);
-
-    // Virtual field (doesn't serialize anything, just makes the value available)
-    boolean extended = getExtended();
-    writeBuffer.writeVirtual("extended", extended);
-
-    // Virtual field (doesn't serialize anything, just makes the value available)
-    boolean remote = getRemote();
-    writeBuffer.writeVirtual("remote", remote);
-
-    // Virtual field (doesn't serialize anything, just makes the value available)
-    boolean error = getError();
-    writeBuffer.writeVirtual("error", error);
-
-    // Implicit Field (size) (Used for parsing, but its value is not stored as it's implicitly given
-    // by the objects content)
-    short size = (short) (COUNT(getData()));
-    writeImplicitField(
-        "size",
-        size,
-        writeUnsignedShort(writeBuffer, 8),
-        WithOption.WithByteOrder(ByteOrder.LITTLE_ENDIAN));
-
-    // Reserved Field (reserved)
-    writeReservedField(
-        "reserved",
-        (short) 0x0,
-        writeUnsignedShort(writeBuffer, 8),
-        WithOption.WithByteOrder(ByteOrder.LITTLE_ENDIAN));
-
-    // Reserved Field (reserved)
-    writeReservedField(
-        "reserved",
-        (short) 0x0,
-        writeUnsignedShort(writeBuffer, 8),
-        WithOption.WithByteOrder(ByteOrder.LITTLE_ENDIAN));
-
-    // Reserved Field (reserved)
-    writeReservedField(
-        "reserved",
-        (short) 0x0,
-        writeUnsignedShort(writeBuffer, 8),
-        WithOption.WithByteOrder(ByteOrder.LITTLE_ENDIAN));
-
-    // Array Field (data)
-    writeByteArrayField(
-        "data",
-        data,
-        writeByteArray(writeBuffer, 8),
-        WithOption.WithByteOrder(ByteOrder.LITTLE_ENDIAN));
-
-    // Padding Field (padding)
-    writePaddingField(
-        "padding",
-        (int) ((8) - ((COUNT(data)))),
-        (short) 0x00,
-        writeUnsignedShort(writeBuffer, 8),
-        WithOption.WithByteOrder(ByteOrder.LITTLE_ENDIAN));
-
-    writeBuffer.popContext("SocketCANFrame");
+    // Simple Field: rawId
+    int rawId = FieldReaderFactory.readSimpleField(DataReaderFactory.readSignedInt(readBuffer, 32), WithOption.WithName("rawId"), WithOption.WithFloatEncoding("IEEE754"), WithOption.WithSignedIntegerEncoding("twos-complement"), WithOption.WithUnsignedIntegerEncoding("unsigned-binary"), WithByteBasedOption.WithByteOrder("LITTLE_ENDIAN"), WithOption.WithStringEncoding("UTF8"));
+
+    // Virtual Field: identifier (doesn't parse anything, just makes the value available)
+    int identifier = FieldReaderFactory.readVirtualField(int.class, StaticHelper.readIdentifier(rawId), WithOption.WithName("identifier"), WithOption.WithFloatEncoding("IEEE754"), WithOption.WithSignedIntegerEncoding("twos-complement"), WithOption.WithUnsignedIntegerEncoding("unsigned-binary"), WithByteBasedOption.WithByteOrder("LITTLE_ENDIAN"), WithOption.WithStringEncoding("UTF8"));
+
+    // Virtual Field: extended (doesn't parse anything, just makes the value available)
+    boolean extended = FieldReaderFactory.readVirtualField(boolean.class, StaticHelper.isExtended(rawId), WithOption.WithName("extended"), WithOption.WithFloatEncoding("IEEE754"), WithOption.WithSignedIntegerEncoding("twos-complement"), WithOption.WithUnsignedIntegerEncoding("unsigned-binary"), WithByteBasedOption.WithByteOrder("LITTLE_ENDIAN"), WithOption.WithStringEncoding("UTF8"));
+
+    // Virtual Field: remote (doesn't parse anything, just makes the value available)
+    boolean remote = FieldReaderFactory.readVirtualField(boolean.class, StaticHelper.isRemote(rawId), WithOption.WithName("remote"), WithOption.WithFloatEncoding("IEEE754"), WithOption.WithSignedIntegerEncoding("twos-complement"), WithOption.WithUnsignedIntegerEncoding("unsigned-binary"), WithByteBasedOption.WithByteOrder("LITTLE_ENDIAN"), WithOption.WithStringEncoding("UTF8"));
+
+    // Virtual Field: error (doesn't parse anything, just makes the value available)
+    boolean error = FieldReaderFactory.readVirtualField(boolean.class, StaticHelper.isError(rawId), WithOption.WithName("error"), WithOption.WithFloatEncoding("IEEE754"), WithOption.WithSignedIntegerEncoding("twos-complement"), WithOption.WithUnsignedIntegerEncoding("unsigned-binary"), WithByteBasedOption.WithByteOrder("LITTLE_ENDIAN"), WithOption.WithStringEncoding("UTF8"));
+
+    // Implicit Field: size
+    short size = FieldReaderFactory.readImplicitField(DataReaderFactory.readUnsignedShort(readBuffer, 8), WithOption.WithName("size"), WithOption.WithFloatEncoding("IEEE754"), WithOption.WithSignedIntegerEncoding("twos-complement"), WithOption.WithUnsignedIntegerEncoding("unsigned-binary"), WithByteBasedOption.WithByteOrder("LITTLE_ENDIAN"), WithOption.WithStringEncoding("UTF8"));
+
+    // Reserved Field
+    FieldReaderFactory.readReservedField(DataReaderFactory.readUnsignedShort(readBuffer, 8), (short) 0x0, WithOption.WithName("SocketCANFrame.reserved6"));
+
+    // Reserved Field
+    FieldReaderFactory.readReservedField(DataReaderFactory.readUnsignedShort(readBuffer, 8), (short) 0x0, WithOption.WithName("SocketCANFrame.reserved7"));
+
+    // Reserved Field
+    FieldReaderFactory.readReservedField(DataReaderFactory.readUnsignedShort(readBuffer, 8), (short) 0x0, WithOption.WithName("SocketCANFrame.reserved8"));
+
+    // Array Field: data
+    byte[] data = readBuffer.readBits(Math.toIntExact((size) * 8), WithOption.WithName("data"), WithOption.WithFloatEncoding("IEEE754"), WithOption.WithSignedIntegerEncoding("twos-complement"), WithOption.WithUnsignedIntegerEncoding("unsigned-binary"), WithByteBasedOption.WithByteOrder("LITTLE_ENDIAN"), WithOption.WithStringEncoding("UTF8"));
+
+    // Padding Field: padding10
+    FieldReaderFactory.readPaddingField(DataReaderFactory.readUnsignedShort(readBuffer, 8), (int) (8) - ((org.apache.plc4x.java.spi.utils.StaticHelper.COUNT(data))));
+
+    readBuffer.popContext();
+    return new SocketCANFrame(rawId, data);
+  }
+
+  @Override
+  public void serialize(WriteBuffer writeBuffer) throws BufferException {
+    writeBuffer.pushContext(WithOption.WithName("SocketCANFrame"), WithOption.WithFloatEncoding("IEEE754"), WithOption.WithSignedIntegerEncoding("twos-complement"), WithOption.WithUnsignedIntegerEncoding("unsigned-binary"), WithByteBasedOption.WithByteOrder("LITTLE_ENDIAN"), WithOption.WithStringEncoding("UTF8"));
+    int startPos = writeBuffer.getPositionInBits();
+    boolean _lastItem = ThreadLocalHelper.lastItemThreadLocal.get();
+    // Simple Field: rawId
+    FieldWriterFactory.writeSimpleField((int) rawId, DataWriterFactory.writeSignedInt(writeBuffer, 32), WithOption.WithName("rawId"), WithOption.WithFloatEncoding("IEEE754"), WithOption.WithSignedIntegerEncoding("twos-complement"), WithOption.WithUnsignedIntegerEncoding("unsigned-binary"), WithByteBasedOption.WithByteOrder("LITTLE_ENDIAN"), WithOption.WithStringEncoding("UTF8"));
+
+    // Virtual Field: identifier (doesn't serialize anything, just makes the value available)
+    int identifier = (int) getIdentifier();
+
+    // Virtual Field: extended (doesn't serialize anything, just makes the value available)
+    boolean extended = (boolean) getExtended();
+
+    // Virtual Field: remote (doesn't serialize anything, just makes the value available)
+    boolean remote = (boolean) getRemote();
+
+    // Virtual Field: error (doesn't serialize anything, just makes the value available)
+    boolean error = (boolean) getError();
+
+    // Implicit Field: size
+    short size = (short) (org.apache.plc4x.java.spi.utils.StaticHelper.COUNT(data));
+    FieldWriterFactory.writeImplicitField((short) size, DataWriterFactory.writeUnsignedShort(writeBuffer, 8), WithOption.WithName("size"), WithOption.WithFloatEncoding("IEEE754"), WithOption.WithSignedIntegerEncoding("twos-complement"), WithOption.WithUnsignedIntegerEncoding("unsigned-binary"), WithByteBasedOption.WithByteOrder("LITTLE_ENDIAN"), WithOption.WithStringEncoding("UTF8"));
+
+    // Reserved Field
+    FieldWriterFactory.writeReservedField((short) 0x0, DataWriterFactory.writeUnsignedShort(writeBuffer, 8));
+
+    // Reserved Field
+    FieldWriterFactory.writeReservedField((short) 0x0, DataWriterFactory.writeUnsignedShort(writeBuffer, 8));
+
+    // Reserved Field
+    FieldWriterFactory.writeReservedField((short) 0x0, DataWriterFactory.writeUnsignedShort(writeBuffer, 8));
+
+    // Array Field: data
+    FieldWriterFactory.writeByteArrayField(data, DataWriterFactory.writeByteArray(writeBuffer, (int) ((data != null) ? data.length : 0)), WithOption.WithName("data"), WithOption.WithFloatEncoding("IEEE754"), WithOption.WithSignedIntegerEncoding("twos-complement"), WithOption.WithUnsignedIntegerEncoding("unsigned-binary"), WithByteBasedOption.WithByteOrder("LITTLE_ENDIAN"), WithOption.WithStringEncoding("UTF8"));
+
+    // Padding Field: padding10
+    FieldWriterFactory.writePaddingField((int) (8) - ((org.apache.plc4x.java.spi.utils.StaticHelper.COUNT(data))), (short) 0x00, DataWriterFactory.writeUnsignedShort(writeBuffer, 8));
+
+    writeBuffer.popContext();
   }
 
   @Override
@@ -164,147 +177,35 @@ public class SocketCANFrame implements Message {
     int lengthInBits = 0;
     SocketCANFrame _value = this;
     boolean _lastItem = ThreadLocalHelper.lastItemThreadLocal.get();
-
-    // Simple field (rawId)
+    // Simple Field: rawId
     lengthInBits += 32;
 
-    // A virtual field doesn't have any in- or output.
+    // Virtual Field: identifier (doesn't produce any output, just makes the value available)
 
-    // A virtual field doesn't have any in- or output.
+    // Virtual Field: extended (doesn't produce any output, just makes the value available)
 
-    // A virtual field doesn't have any in- or output.
+    // Virtual Field: remote (doesn't produce any output, just makes the value available)
 
-    // A virtual field doesn't have any in- or output.
+    // Virtual Field: error (doesn't produce any output, just makes the value available)
 
-    // Implicit Field (size)
+    // Implicit Field: size
     lengthInBits += 8;
 
-    // Reserved Field (reserved)
+    // Reserved Field
     lengthInBits += 8;
 
-    // Reserved Field (reserved)
+    // Reserved Field
     lengthInBits += 8;
 
-    // Reserved Field (reserved)
+    // Reserved Field
     lengthInBits += 8;
 
-    // Array field
-    if (data != null) {
-      lengthInBits += 8 * data.length;
-    }
+    // Array Field: data
+    lengthInBits += 8 * ((data != null) ? data.length : 0);
 
-    // Padding Field (padding)
-    int _timesPadding = (int) ((8) - ((COUNT(data))));
-    while (_timesPadding-- > 0) {
-      lengthInBits += 8;
-    }
+    // Padding Field: padding10
+    lengthInBits += 8 * ((int) (8) - ((org.apache.plc4x.java.spi.utils.StaticHelper.COUNT(data))));
 
     return lengthInBits;
   }
-
-  public static SocketCANFrame staticParse(ReadBuffer readBuffer) throws ParseException {
-    readBuffer.pullContext("SocketCANFrame");
-    PositionAware positionAware = readBuffer;
-    boolean _lastItem = ThreadLocalHelper.lastItemThreadLocal.get();
-
-    int rawId =
-        readSimpleField(
-            "rawId",
-            readSignedInt(readBuffer, 32),
-            WithOption.WithByteOrder(ByteOrder.LITTLE_ENDIAN));
-    int identifier =
-        readVirtualField(
-            "identifier",
-            int.class,
-            org.apache.plc4x.java.socketcan.readwrite.utils.StaticHelper.readIdentifier(rawId),
-            WithOption.WithByteOrder(ByteOrder.LITTLE_ENDIAN));
-    boolean extended =
-        readVirtualField(
-            "extended",
-            boolean.class,
-            org.apache.plc4x.java.socketcan.readwrite.utils.StaticHelper.isExtended(rawId),
-            WithOption.WithByteOrder(ByteOrder.LITTLE_ENDIAN));
-    boolean remote =
-        readVirtualField(
-            "remote",
-            boolean.class,
-            org.apache.plc4x.java.socketcan.readwrite.utils.StaticHelper.isRemote(rawId),
-            WithOption.WithByteOrder(ByteOrder.LITTLE_ENDIAN));
-    boolean error =
-        readVirtualField(
-            "error",
-            boolean.class,
-            org.apache.plc4x.java.socketcan.readwrite.utils.StaticHelper.isError(rawId),
-            WithOption.WithByteOrder(ByteOrder.LITTLE_ENDIAN));
-
-    short size =
-        readImplicitField(
-            "size",
-            readUnsignedShort(readBuffer, 8),
-            WithOption.WithByteOrder(ByteOrder.LITTLE_ENDIAN));
-
-    Short reservedField0 =
-        readReservedField(
-            "reserved",
-            readUnsignedShort(readBuffer, 8),
-            (short) 0x0,
-            WithOption.WithByteOrder(ByteOrder.LITTLE_ENDIAN));
-
-    Short reservedField1 =
-        readReservedField(
-            "reserved",
-            readUnsignedShort(readBuffer, 8),
-            (short) 0x0,
-            WithOption.WithByteOrder(ByteOrder.LITTLE_ENDIAN));
-
-    Short reservedField2 =
-        readReservedField(
-            "reserved",
-            readUnsignedShort(readBuffer, 8),
-            (short) 0x0,
-            WithOption.WithByteOrder(ByteOrder.LITTLE_ENDIAN));
-
-    byte[] data =
-        readBuffer.readByteArray(
-            "data", Math.toIntExact(size), WithOption.WithByteOrder(ByteOrder.LITTLE_ENDIAN));
-
-    readPaddingField(
-        readUnsignedShort(readBuffer, 8),
-        (int) ((8) - ((COUNT(data)))),
-        WithOption.WithByteOrder(ByteOrder.LITTLE_ENDIAN));
-
-    readBuffer.closeContext("SocketCANFrame");
-    // Create the instance
-    SocketCANFrame _socketCANFrame;
-    _socketCANFrame = new SocketCANFrame(rawId, data);
-    return _socketCANFrame;
-  }
-
-  @Override
-  public boolean equals(Object o) {
-    if (this == o) {
-      return true;
-    }
-    if (!(o instanceof SocketCANFrame)) {
-      return false;
-    }
-    SocketCANFrame that = (SocketCANFrame) o;
-    return (getRawId() == that.getRawId()) && (getData() == that.getData()) && true;
-  }
-
-  @Override
-  public int hashCode() {
-    return Objects.hash(getRawId(), getData());
-  }
-
-  @Override
-  public String toString() {
-    WriteBufferBoxBased writeBufferBoxBased = new WriteBufferBoxBased(true, true);
-    try {
-      writeBufferBoxBased.writeSerializable(this);
-    } catch (SerializationException e) {
-      throw new RuntimeException(e);
-    }
-    return "\n" + writeBufferBoxBased.getBox().toString() + "\n";
-  }
 }
diff --git a/protocols/canopen/src/main/resources/protocols/can/canopen.mspec b/protocols/canopen/src/main/resources/protocols/can/canopen.mspec
index cc674a0388..720dba76b6 100644
--- a/protocols/canopen/src/main/resources/protocols/can/canopen.mspec
+++ b/protocols/canopen/src/main/resources/protocols/can/canopen.mspec
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-[enum uint 4 CANOpenService(uint 8 min, uint 8 max, bit pdo)
+[enum uint 4 CANOpenService(uint 8 min, uint 8 max, bit pdo) byteOrder='"LITTLE_ENDIAN"' unsignedIntegerEncoding='"unsigned-binary"' signedIntegerEncoding='"twos-complement"' floatEncoding='"IEEE754"' stringEncoding='"UTF8"'
     ['0b0000' NMT             ['0',          '0'    ,      'false']]
     ['0b0001' SYNC            ['0x80',       '0x80' ,      'false']]
     ['0b0001' EMCY            ['0x81',       '0xFF' ,      'false']]
@@ -35,7 +35,7 @@
     ['0b1110' HEARTBEAT       ['0x700',      '0x77F',      'false']]
 ]
 
-[enum uint 8 NMTStateRequest
+[enum uint 8 NMTStateRequest byteOrder='"LITTLE_ENDIAN"' unsignedIntegerEncoding='"unsigned-binary"' signedIntegerEncoding='"twos-complement"' floatEncoding='"IEEE754"' stringEncoding='"UTF8"'
     ['0x01' START]
     ['0x02' STOP]
     ['0x80' PRE_OPERATIONAL]
@@ -43,14 +43,14 @@
     ['0x82' RESET_COMMUNICATION]
 ]
 
-[enum uint 8 NMTState
+[enum uint 8 NMTState byteOrder='"LITTLE_ENDIAN"' unsignedIntegerEncoding='"unsigned-binary"' signedIntegerEncoding='"twos-complement"' floatEncoding='"IEEE754"' stringEncoding='"UTF8"'
     ['0x00' BOOTED_UP]
     ['0x04' STOPPED]
     ['0x05' OPERATIONAL]
     ['0x7f' PRE_OPERATIONAL]
 ]
 
-[discriminatedType CANOpenPayload(CANOpenService service)
+[discriminatedType CANOpenPayload(CANOpenService service) byteOrder='"LITTLE_ENDIAN"' unsignedIntegerEncoding='"unsigned-binary"' signedIntegerEncoding='"twos-complement"' floatEncoding='"IEEE754"' stringEncoding='"UTF8"'
     [typeSwitch service
         ['NMT' CANOpenNetworkPayload
             [simple NMTStateRequest request]
@@ -98,7 +98,7 @@
     ]
 ]
 
-[type SDORequest(SDORequestCommand command)
+[type SDORequest(SDORequestCommand command) byteOrder='"LITTLE_ENDIAN"' unsignedIntegerEncoding='"unsigned-binary"' signedIntegerEncoding='"twos-complement"' floatEncoding='"IEEE754"' stringEncoding='"UTF8"'
     [typeSwitch command
         ['SEGMENT_DOWNLOAD' SDOSegmentDownloadRequest
             [simple bit toggle]
@@ -134,12 +134,12 @@
     ]
 ]
 
-[type SDOBlockData
+[type SDOBlockData byteOrder='"LITTLE_ENDIAN"' unsignedIntegerEncoding='"unsigned-binary"' signedIntegerEncoding='"twos-complement"' floatEncoding='"IEEE754"' stringEncoding='"UTF8"'
     [simple uint 5 flags]
     [array byte data count '7']
 ]
 
-[type SDOResponse(SDOResponseCommand command)
+[type SDOResponse(SDOResponseCommand command) byteOrder='"LITTLE_ENDIAN"' unsignedIntegerEncoding='"unsigned-binary"' signedIntegerEncoding='"twos-complement"' floatEncoding='"IEEE754"' stringEncoding='"UTF8"'
     [typeSwitch command
         ['SEGMENT_UPLOAD' SDOSegmentUploadResponse
             [simple bit toggle]
@@ -175,7 +175,7 @@
     ]
 ]
 
-[type SDOInitiateUploadResponsePayload(bit expedited, bit indicated, uint 2 size)
+[type SDOInitiateUploadResponsePayload(bit expedited, bit indicated, uint 2 size) byteOrder='"LITTLE_ENDIAN"' unsignedIntegerEncoding='"unsigned-binary"' signedIntegerEncoding='"twos-complement"' floatEncoding='"IEEE754"' stringEncoding='"UTF8"'
     [typeSwitch expedited, indicated
         ['true', 'true' SDOInitiateExpeditedUploadResponse
             [array byte data count '4 - size']
@@ -190,13 +190,13 @@
     ]
 ]
 
-[type SDOAbort
+[type SDOAbort byteOrder='"LITTLE_ENDIAN"' unsignedIntegerEncoding='"unsigned-binary"' signedIntegerEncoding='"twos-complement"' floatEncoding='"IEEE754"' stringEncoding='"UTF8"'
     [reserved uint 5 '0x00']
     [simple IndexAddress address]
     [simple uint 32 code]
 ]
 
-[type SDOSegment
+[type SDOSegment byteOrder='"LITTLE_ENDIAN"' unsignedIntegerEncoding='"unsigned-binary"' signedIntegerEncoding='"twos-complement"' floatEncoding='"IEEE754"' stringEncoding='"UTF8"'
     [reserved uint 1 '0x00']
     [implicit uint 2 size 'expedited && indicated ? 4 - COUNT(data) : 0']
     [simple bit expedited]
@@ -206,12 +206,12 @@
     [padding uint 8 alignment '0x00' '4 - (COUNT(data))']
 ]
 
-[type IndexAddress
+[type IndexAddress byteOrder='"LITTLE_ENDIAN"' unsignedIntegerEncoding='"unsigned-binary"' signedIntegerEncoding='"twos-complement"' floatEncoding='"IEEE754"' stringEncoding='"UTF8"'
     [simple uint 16 index]
     [simple uint 8 subindex]
 ]
 
-[enum uint 3 SDORequestCommand
+[enum uint 3 SDORequestCommand byteOrder='"LITTLE_ENDIAN"' unsignedIntegerEncoding='"unsigned-binary"' signedIntegerEncoding='"twos-complement"' floatEncoding='"IEEE754"' stringEncoding='"UTF8"'
     ['0x00' SEGMENT_DOWNLOAD  ]
     ['0x01' INITIATE_DOWNLOAD ]
     ['0x02' INITIATE_UPLOAD   ]
@@ -220,7 +220,7 @@
     ['0x05' BLOCK             ]
 ]
 
-[enum uint 3 SDOResponseCommand
+[enum uint 3 SDOResponseCommand byteOrder='"LITTLE_ENDIAN"' unsignedIntegerEncoding='"unsigned-binary"' signedIntegerEncoding='"twos-complement"' floatEncoding='"IEEE754"' stringEncoding='"UTF8"'
     ['0x00' SEGMENT_UPLOAD    ]
     ['0x01' SEGMENT_DOWNLOAD  ]
     ['0x02' INITIATE_UPLOAD   ]
@@ -229,18 +229,18 @@
     ['0x06' BLOCK             ]
 ]
 
-[type CANOpenPDO /*(uint 2 index, bit receive)*/
+[type CANOpenPDO /*(uint 2 index, bit receive)*/ byteOrder='"LITTLE_ENDIAN"' unsignedIntegerEncoding='"unsigned-binary"' signedIntegerEncoding='"twos-complement"' floatEncoding='"IEEE754"' stringEncoding='"UTF8"'
     [array byte data count '8']
 ]
 
-[type CANOpenTime
+[type CANOpenTime byteOrder='"LITTLE_ENDIAN"' unsignedIntegerEncoding='"unsigned-binary"' signedIntegerEncoding='"twos-complement"' floatEncoding='"IEEE754"' stringEncoding='"UTF8"'
     // CiA 301 - section 7.1.6.5 and 7.1.6.6
     [simple  uint 32 millis]
     [virtual uint 28 cleanMillis 'millis & 0x0FFFFFFF']
     [simple  uint 16 days]
 ]
 
-[enum CANOpenDataType (uint 8 numBits, vstring plcValueName)
+[enum CANOpenDataType (uint 8 numBits, vstring plcValueName) byteOrder='"LITTLE_ENDIAN"' unsignedIntegerEncoding='"unsigned-binary"' signedIntegerEncoding='"twos-complement"' floatEncoding='"IEEE754"' stringEncoding='"UTF8"'
     [BOOLEAN     [ '1', '"BIT"'           ] ]
     [UNSIGNED8   [ '8', '"USINT"'         ] ]
     [UNSIGNED16  ['16', '"UINT"'          ] ]
@@ -270,7 +270,7 @@
     [TIME_DIFFERENCE  ['48', '"TIME"'       ] ]
 ]
 
-[dataIo DataItem(CANOpenDataType dataType, int 32 size)
+[dataIo DataItem(CANOpenDataType dataType, int 32 size) byteOrder='"LITTLE_ENDIAN"' unsignedIntegerEncoding='"unsigned-binary"' signedIntegerEncoding='"twos-complement"' floatEncoding='"IEEE754"' stringEncoding='"UTF8"'
     [typeSwitch dataType
         ['BOOLEAN' BOOL
             [simple bit value]
@@ -347,14 +347,14 @@
 ]
 
 // utility type quickly write data for mapped/manufacturer PDOs
-[type CANOpenMPDO
+[type CANOpenMPDO byteOrder='"LITTLE_ENDIAN"' unsignedIntegerEncoding='"unsigned-binary"' signedIntegerEncoding='"twos-complement"' floatEncoding='"IEEE754"' stringEncoding='"UTF8"'
     [simple uint 8 node]
     [simple IndexAddress address]
     [array byte data count '4']
 ]
 
 // A compact, byte aligned structure for test and embedding purposes
-[type CANOpenFrame byteOrder='"LITTLE_ENDIAN"'
+[type CANOpenFrame byteOrder='"LITTLE_ENDIAN"' unsignedIntegerEncoding='"unsigned-binary"' signedIntegerEncoding='"twos-complement"' floatEncoding='"IEEE754"' stringEncoding='"UTF8"'
     [simple uint 8 nodeId]
     [simple CANOpenService service]
     [const uint 4 alignment 0x00]
diff --git a/protocols/socketcan/src/main/resources/protocols/can/socketcan.mspec b/protocols/socketcan/src/main/resources/protocols/can/socketcan.mspec
index 5941979cbe..7cc0f5b34c 100644
--- a/protocols/socketcan/src/main/resources/protocols/can/socketcan.mspec
+++ b/protocols/socketcan/src/main/resources/protocols/can/socketcan.mspec
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-[type SocketCANFrame byteOrder='"LITTLE_ENDIAN"'
+[type SocketCANFrame byteOrder='"LITTLE_ENDIAN"' unsignedIntegerEncoding='"unsigned-binary"' signedIntegerEncoding='"twos-complement"' floatEncoding='"IEEE754"' stringEncoding='"UTF8"'
     [simple     int 32  rawId                                               ]
     [virtual    int 32  identifier  'STATIC_CALL("readIdentifier", rawId)'  ]
     [virtual    bit     extended    'STATIC_CALL("isExtended", rawId)'      ]