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:<transport>://...}</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)' ]