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