This is an automated email from the ASF dual-hosted git repository. ldywicki pushed a commit to branch feature/socketcan-0.8-preparations in repository https://gitbox.apache.org/repos/asf/plc4x.git
commit 149a5c9195d6206d4564797ce0b8440804c0e05d Author: Ćukasz Dywicki <[email protected]> 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
