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

Reply via email to