This is an automated email from the ASF dual-hosted git repository. ldywicki pushed a commit to branch issue/2468 in repository https://gitbox.apache.org/repos/asf/plc4x.git
commit 7d48aa8f6e4d30e64760eaf4e045040898d3bee5 Author: Łukasz Dywicki <[email protected]> AuthorDate: Sun Mar 15 11:04:47 2026 +0400 feat(plc4j): Support for CANopen NMT commands. Fixes #2468. Re-enabling tests for SDO operations. Signed-off-by: Łukasz Dywicki <[email protected]> --- .../canopen/protocol/CANOpenProtocolLogic.java | 27 +++ .../plc4x/java/canopen/tag/CANOpenNMTTag.java | 4 +- ...penDriverSDOIT.java => CANOpenDriverNMTIT.java} | 7 +- .../plc4x/java/canopen/CANOpenDriverSDOIT.java | 1 - .../protocols/canopen/CANOpenDriverNMTIT.xml | 122 ++++++++++++++ .../protocols/canopen/CANOpenDriverSDOIT.xml | 181 ++++++++++++--------- .../modules/users/pages/protocols/canopen.adoc | 13 +- 7 files changed, 267 insertions(+), 88 deletions(-) diff --git a/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/protocol/CANOpenProtocolLogic.java b/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/protocol/CANOpenProtocolLogic.java index 9124cab003..2dadd4d4b2 100644 --- a/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/protocol/CANOpenProtocolLogic.java +++ b/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/protocol/CANOpenProtocolLogic.java @@ -191,6 +191,10 @@ public class CANOpenProtocolLogic extends Plc4xCANProtocolBase<CANOpenFrame> writeInternally((DefaultPlcWriteRequest) writeRequest, (CANOpenPDOTag) tag, response); return response; } + if (tag instanceof CANOpenNMTTag) { + writeInternally((DefaultPlcWriteRequest) writeRequest, (CANOpenNMTTag) tag, response); + return response; + } response.completeExceptionally(new IllegalArgumentException("Only CANOpenSDOTag instances are supported")); return response; @@ -238,6 +242,29 @@ public class CANOpenProtocolLogic extends Plc4xCANProtocolBase<CANOpenFrame> } } + private void writeInternally(DefaultPlcWriteRequest writeRequest, CANOpenNMTTag tag, CompletableFuture<PlcWriteResponse> response) { + PlcValue writeValue = writeRequest.getPlcValues().get(0); + + NMTStateRequest request = null; + if (writeValue.isInteger() || writeValue.isShort() || writeValue.isByte()) { + request = NMTStateRequest.enumForValue(writeValue.getShort()); + } + if (request == null) { + response.completeExceptionally(new PlcRuntimeException("Unsupported NMTStateRequest value: " + writeValue)); + return; + } + + try { + String tagName = writeRequest.getTagNames().iterator().next(); + + final CANOpenNetworkPayload payload = new CANOpenNetworkPayload(request, Integer.valueOf(tag.getNodeId()).byteValue()); + conversationContext.sendToWire(new CANOpenFrame((short) 0, tag.getService(), payload)); + response.complete(new DefaultPlcWriteResponse(writeRequest, Collections.singletonMap(tagName, PlcResponseCode.OK))); + } catch (Exception e) { + response.completeExceptionally(e); + } + } + public CompletableFuture<PlcReadResponse> read(PlcReadRequest readRequest) { CompletableFuture<PlcReadResponse> response = new CompletableFuture<>(); if (readRequest.getTagNames().size() != 1) { diff --git a/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/tag/CANOpenNMTTag.java b/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/tag/CANOpenNMTTag.java index 6f93a585cd..bd8a22e5e2 100644 --- a/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/tag/CANOpenNMTTag.java +++ b/plc4j/drivers/canopen/src/main/java/org/apache/plc4x/java/canopen/tag/CANOpenNMTTag.java @@ -51,12 +51,12 @@ public class CANOpenNMTTag extends CANOpenTag implements CANOpenSubscriptionTag @Override public String getAddressString() { - return "NMT:" + getNodeId(); + return isWildcard() ? "NMT" : "NMT:" + getNodeId(); } @Override public PlcValueType getPlcValueType() { - return PlcValueType.NULL; + return PlcValueType.USINT; } @Override diff --git a/plc4j/drivers/canopen/src/test/java/org/apache/plc4x/java/canopen/CANOpenDriverSDOIT.java b/plc4j/drivers/canopen/src/test/java/org/apache/plc4x/java/canopen/CANOpenDriverNMTIT.java similarity index 84% copy from plc4j/drivers/canopen/src/test/java/org/apache/plc4x/java/canopen/CANOpenDriverSDOIT.java copy to plc4j/drivers/canopen/src/test/java/org/apache/plc4x/java/canopen/CANOpenDriverNMTIT.java index 764486be76..8c7eaac300 100644 --- a/plc4j/drivers/canopen/src/test/java/org/apache/plc4x/java/canopen/CANOpenDriverSDOIT.java +++ b/plc4j/drivers/canopen/src/test/java/org/apache/plc4x/java/canopen/CANOpenDriverNMTIT.java @@ -21,11 +21,10 @@ package org.apache.plc4x.java.canopen; import org.apache.plc4x.test.driver.DriverTestsuiteRunner; import org.junit.jupiter.api.Disabled; -@Disabled("Fix this") -class CANOpenDriverSDOIT extends DriverTestsuiteRunner { +class CANOpenDriverNMTIT extends DriverTestsuiteRunner { - public CANOpenDriverSDOIT() { - super("/protocols/canopen/CANOpenDriverSDOIT.xml", true); + public CANOpenDriverNMTIT() { + super("/protocols/canopen/CANOpenDriverNMTIT.xml", true); } } diff --git a/plc4j/drivers/canopen/src/test/java/org/apache/plc4x/java/canopen/CANOpenDriverSDOIT.java b/plc4j/drivers/canopen/src/test/java/org/apache/plc4x/java/canopen/CANOpenDriverSDOIT.java index 764486be76..61a7b22995 100644 --- a/plc4j/drivers/canopen/src/test/java/org/apache/plc4x/java/canopen/CANOpenDriverSDOIT.java +++ b/plc4j/drivers/canopen/src/test/java/org/apache/plc4x/java/canopen/CANOpenDriverSDOIT.java @@ -21,7 +21,6 @@ package org.apache.plc4x.java.canopen; import org.apache.plc4x.test.driver.DriverTestsuiteRunner; import org.junit.jupiter.api.Disabled; -@Disabled("Fix this") class CANOpenDriverSDOIT extends DriverTestsuiteRunner { public CANOpenDriverSDOIT() { diff --git a/protocols/canopen/src/test/resources/protocols/canopen/CANOpenDriverNMTIT.xml b/protocols/canopen/src/test/resources/protocols/canopen/CANOpenDriverNMTIT.xml new file mode 100644 index 0000000000..3a12d03cd6 --- /dev/null +++ b/protocols/canopen/src/test/resources/protocols/canopen/CANOpenDriverNMTIT.xml @@ -0,0 +1,122 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + https://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + --> +<test:driver-testsuite xmlns:test="https://plc4x.apache.org/schemas/driver-testsuite.xsd" + byteOrder="LITTLE_ENDIAN"> + + <name>CANOpen SDO Segmentation</name> + + <protocolName>canopen</protocolName> + <outputFlavor>read-write</outputFlavor> + + <driver-name>canopen</driver-name> + + <driver-parameters> + <parameter> + <name>nodeId</name> + <value>15</value> + </parameter> + <parameter> + <name>request-timeout</name> + <value>5000</value> + </parameter> + </driver-parameters> + + <testcase> + <name>Send NMT write request</name> + <description> + Trigger write using CANOpen NMT requesting specific node to change its state. + </description> + <steps> + <api-request name="Request node 1 to enter PRE_OPERATIONAL state"> + <TestWriteRequest> + <tags isList="true"> + <tag className="org.apache.plc4x.test.driver.internal.api.TestValueTag"> + <name>boot_up_request</name> + <address>NMT:1</address> + <value> + <PlcUSINT>128</PlcUSINT> + </value> + </tag> + </tags> + </TestWriteRequest> + </api-request> + <outgoing-plc-message name="Send NMT Request"> + <CANOpenFrame> + <nodeId dataType="uint" bitLength="8">0</nodeId> + <service> + <CANOpenService dataType="uint" bitLength="4" stringRepresentation="NMT">0</CANOpenService> + </service> + <alignment dataType="uint" bitLength="4">0</alignment> + <payload> + <CANOpenPayload> + <CANOpenNetworkPayload> + <request> + <NMTStateRequest dataType="uint" bitLength="8" stringRepresentation="PRE_OPERATIONAL">128</NMTStateRequest> + </request> + <reserved dataType="uint" bitLength="1">0</reserved> + <node dataType="uint" bitLength="7">1</node> + </CANOpenNetworkPayload> + </CANOpenPayload> + </payload> + <padding isList="true"> + <value dataType="uint" bitLength="8">0</value> + <value dataType="uint" bitLength="8">0</value> + <value dataType="uint" bitLength="8">0</value> + <value dataType="uint" bitLength="8">0</value> + <value dataType="uint" bitLength="8">0</value> + <value dataType="uint" bitLength="8">0</value> + </padding> + </CANOpenFrame> + </outgoing-plc-message> + <api-response name="Report Write Response to application"> + <PlcWriteResponse> + <request> + <PlcWriteRequest> + <PlcTagRequest> + <tags isList="true"> + <boot_up_request> + <PlcTagValueItem> + <tag> + <CANOpenNMTTag> + <service dataType="string" bitLength="24" encoding="UTF-8">NMT</service> + <node dataType="int" bitLength="64">1</node> + </CANOpenNMTTag> + </tag> + <value> + <PlcUSINT dataType="int" bitLength="8">128</PlcUSINT> + </value> + </PlcTagValueItem> + </boot_up_request> + </tags> + </PlcTagRequest> + </PlcWriteRequest> + </request> + <responseCodes isList="true"> + <boot_up_request> + <ResponseCode dataType="uint" bitLength="8" stringRepresentation="OK">1</ResponseCode> + </boot_up_request> + </responseCodes> + </PlcWriteResponse> + </api-response> + <delay>1000</delay> + </steps> + </testcase> + +</test:driver-testsuite> \ No newline at end of file diff --git a/protocols/canopen/src/test/resources/protocols/canopen/CANOpenDriverSDOIT.xml b/protocols/canopen/src/test/resources/protocols/canopen/CANOpenDriverSDOIT.xml index af0bbb47a1..d9d1906096 100644 --- a/protocols/canopen/src/test/resources/protocols/canopen/CANOpenDriverSDOIT.xml +++ b/protocols/canopen/src/test/resources/protocols/canopen/CANOpenDriverSDOIT.xml @@ -183,20 +183,26 @@ </incoming-plc-message> <api-response name="Report Read Response to application (expedited)"> <PlcReadResponse> - <PlcReadRequest> - <PlcTagRequest> - <tags isList="true"> - <expeditedSdo> - <CANOpenSDOTag> - <node dataType="int" bitLength="64">1</node> - <index dataType="int" bitLength="64">1000</index> - <subIndex dataType="int" bitLength="64">22</subIndex> - <dataType dataType="string" bitLength="80" encoding="UTF-8">UNSIGNED32</dataType> - </CANOpenSDOTag> - </expeditedSdo> - </tags> - </PlcTagRequest> - </PlcReadRequest> + <request> + <PlcReadRequest> + <PlcTagRequest> + <tags isList="true"> + <expeditedSdo> + <PlcTagItem> + <tag> + <CANOpenSDOTag> + <node dataType="int" bitLength="64">1</node> + <index dataType="int" bitLength="64">1000</index> + <subIndex dataType="int" bitLength="64">22</subIndex> + <dataType dataType="string" bitLength="80" encoding="UTF-8">UNSIGNED32</dataType> + </CANOpenSDOTag> + </tag> + </PlcTagItem> + </expeditedSdo> + </tags> + </PlcTagRequest> + </PlcReadRequest> + </request> <values isList="true"> <expeditedSdo> <PlcResponseItem> @@ -312,20 +318,26 @@ </incoming-plc-message> <api-response name="Report Read Response to application (expedited short)"> <PlcReadResponse> - <PlcReadRequest> - <PlcTagRequest> - <tags isList="true"> - <expeditedShort> - <CANOpenSDOTag> - <node dataType="int" bitLength="64">1</node> - <index dataType="int" bitLength="64">9186</index> - <subIndex dataType="int" bitLength="64">1</subIndex> - <dataType dataType="string" bitLength="72" encoding="UTF-8">UNSIGNED8</dataType> - </CANOpenSDOTag> - </expeditedShort> - </tags> - </PlcTagRequest> - </PlcReadRequest> + <request> + <PlcReadRequest> + <PlcTagRequest> + <tags isList="true"> + <expeditedShort> + <PlcTagItem> + <tag> + <CANOpenSDOTag> + <node dataType="int" bitLength="64">1</node> + <index dataType="int" bitLength="64">9186</index> + <subIndex dataType="int" bitLength="64">1</subIndex> + <dataType dataType="string" bitLength="72" encoding="UTF-8">UNSIGNED8</dataType> + </CANOpenSDOTag> + </tag> + </PlcTagItem> + </expeditedShort> + </tags> + </PlcTagRequest> + </PlcReadRequest> + </request> <values isList="true"> <expeditedShort> <PlcResponseItem> @@ -488,20 +500,26 @@ </incoming-plc-message> <api-response name="Report Read Response to application (custom answer id)"> <PlcReadResponse> - <PlcReadRequest> - <PlcTagRequest> - <tags isList="true"> - <expeditedSdoCustomAnswer> - <CANOpenSDOTag> - <node dataType="int" bitLength="64">1</node> - <index dataType="int" bitLength="64">1000</index> - <subIndex dataType="int" bitLength="64">22</subIndex> - <dataType dataType="string" bitLength="80" encoding="UTF-8">UNSIGNED32</dataType> - </CANOpenSDOTag> - </expeditedSdoCustomAnswer> - </tags> - </PlcTagRequest> - </PlcReadRequest> + <request> + <PlcReadRequest> + <PlcTagRequest> + <tags isList="true"> + <expeditedSdoCustomAnswer> + <PlcTagItem> + <tag> + <CANOpenSDOTag> + <node dataType="int" bitLength="64">1</node> + <index dataType="int" bitLength="64">1000</index> + <subIndex dataType="int" bitLength="64">22</subIndex> + <dataType dataType="string" bitLength="80" encoding="UTF-8">UNSIGNED32</dataType> + </CANOpenSDOTag> + </tag> + </PlcTagItem> + </expeditedSdoCustomAnswer> + </tags> + </PlcTagRequest> + </PlcReadRequest> + </request> <values isList="true"> <expeditedSdoCustomAnswer> <PlcResponseItem> @@ -819,35 +837,32 @@ <api-response name="Report Read Response to application (segmented)"> <PlcReadResponse> - <PlcReadRequest> - <PlcTagRequest> - <tags isList="true"> - <segmentedSdo> - <CANOpenSDOTag> - <node dataType="int" bitLength="64">2</node> - <index dataType="int" bitLength="64">2000</index> - <subIndex dataType="int" bitLength="64">44</subIndex> - <dataType dataType="string" bitLength="48" encoding="UTF-8">RECORD</dataType> - </CANOpenSDOTag> - </segmentedSdo> - </tags> - </PlcTagRequest> - </PlcReadRequest> + <request> + <PlcReadRequest> + <PlcTagRequest> + <tags isList="true"> + <segmentedSdo> + <PlcTagItem> + <tag> + <CANOpenSDOTag> + <node dataType="int" bitLength="64">2</node> + <index dataType="int" bitLength="64">2000</index> + <subIndex dataType="int" bitLength="64">44</subIndex> + <dataType dataType="string" bitLength="48" encoding="UTF-8">RECORD</dataType> + </CANOpenSDOTag> + </tag> + </PlcTagItem> + </segmentedSdo> + </tags> + </PlcTagRequest> + </PlcReadRequest> + </request> <values isList="true"> <segmentedSdo> <PlcResponseItem> <code dataType="string" bitLength="16" encoding="UTF-8">OK</code> <value> - <PlcList> - <PlcSINT dataType="byte" bitLength="8">0x61</PlcSINT> - <PlcSINT dataType="byte" bitLength="8">0x73</PlcSINT> - <PlcSINT dataType="byte" bitLength="8">0x64</PlcSINT> - <PlcSINT dataType="byte" bitLength="8">0x66</PlcSINT> - <PlcSINT dataType="byte" bitLength="8">0x61</PlcSINT> - <PlcSINT dataType="byte" bitLength="8">0x73</PlcSINT> - <PlcSINT dataType="byte" bitLength="8">0x64</PlcSINT> - <PlcSINT dataType="byte" bitLength="8">0x66</PlcSINT> - </PlcList> + <PlcRawByteArray dataType="byte" bitLength="64">0x6173646661736466</PlcRawByteArray> </value> </PlcResponseItem> </segmentedSdo> @@ -949,20 +964,26 @@ </incoming-plc-message> <api-response name="Report Read Response to application (abort)"> <PlcReadResponse> - <PlcReadRequest> - <PlcTagRequest> - <tags isList="true"> - <TheFailingTag> - <CANOpenSDOTag> - <node dataType="int" bitLength="64">1</node> - <index dataType="int" bitLength="64">1000</index> - <subIndex dataType="int" bitLength="64">22</subIndex> - <dataType dataType="string" bitLength="80" encoding="UTF-8">UNSIGNED32</dataType> - </CANOpenSDOTag> - </TheFailingTag> - </tags> - </PlcTagRequest> - </PlcReadRequest> + <request> + <PlcReadRequest> + <PlcTagRequest> + <tags isList="true"> + <TheFailingTag> + <PlcTagItem> + <tag> + <CANOpenSDOTag> + <node dataType="int" bitLength="64">1</node> + <index dataType="int" bitLength="64">1000</index> + <subIndex dataType="int" bitLength="64">22</subIndex> + <dataType dataType="string" bitLength="80" encoding="UTF-8">UNSIGNED32</dataType> + </CANOpenSDOTag> + </tag> + </PlcTagItem> + </TheFailingTag> + </tags> + </PlcTagRequest> + </PlcReadRequest> + </request> <values isList="true"> <TheFailingTag> <PlcResponseItem> diff --git a/website/asciidoc/modules/users/pages/protocols/canopen.adoc b/website/asciidoc/modules/users/pages/protocols/canopen.adoc index 771cbedbbc..5ca106189a 100644 --- a/website/asciidoc/modules/users/pages/protocols/canopen.adoc +++ b/website/asciidoc/modules/users/pages/protocols/canopen.adoc @@ -125,12 +125,23 @@ Subscriber will be notified with value mapped to type defined in field syntax. | - NMT - NMT:nodeId | - `subscribe` +- `write` | NMT messages are sent using CAN node ID `0`. They have the highest priority on the bus. -Messages of this kind indicate operating state of an node (booted, operational). +Messages of this kind indicate operating state of a node (booted, operational). Subscriptions to this service receive structure with two fields: `node` (USINT) and `state` (USINT). If subscription sets `nodeId` to 0 it will receive state updates for all bus participants. +Supported write values (USINT): + + * `0x01` -> START + * `0x02` -> STOP + * `0x80` -> PRE_OPERATIONAL + * `0x81` -> RESET_NODE + * `0x82` -> RESET_COMMUNICATION + +On application side, for convenience, you can use `NMTStateRequest` enumeration instead of plain numbers. +Be aware that passed number is parsed as an enum, so vendor customization on unused bits are not supported. | HEARTBEAT | - HEARTBEAT
