From 4ac860baa81f30e3e1fc9aaa42a3f0bb0537543a Mon Sep 17 00:00:00 2001 From: Thomas Vanbesien Date: Mon, 23 Feb 2026 17:35:34 +0100 Subject: Add writeValueAtRange for index-range array writes --- demo/NodePage.qml | 41 +++++++++++++++++++++++++--- src/OpcUaMonitoredNode.cpp | 67 ++++++++++++++++++++++++++++++++++++++++++++++ src/OpcUaMonitoredNode.h | 13 +++++++++ 3 files changed, 118 insertions(+), 3 deletions(-) diff --git a/demo/NodePage.qml b/demo/NodePage.qml index e677232..9b23502 100644 --- a/demo/NodePage.qml +++ b/demo/NodePage.qml @@ -90,6 +90,19 @@ Page { "ns=1;s=bytestring_rw_array" ] }, + { + title: "Index Range Write", + description: "Write to specific array elements using OPC UA index" + + " range syntax. Examples: \"0\" = first element," + + " \"0:2\" = elements 0–2. Enter the range and the value(s)" + + " to write (comma-separated for multi-element ranges).", + indexRange: true, + nodes: [ + "ns=1;s=int32_rw_array", + "ns=1;s=float_rw_array", + "ns=1;s=string_rw_array" + ] + }, { title: "Non-Existent Nodes", description: "These node IDs do not exist on the server." @@ -243,19 +256,41 @@ Page { } // Column 3: Edit area (writable) or READ-ONLY label + + // Normal write controls (non-index-range pages) TextField { id: editField - visible: node.writable + visible: node.writable && !currentPage.indexRange Layout.fillWidth: true placeholderText: "Enter value..." onAccepted: node.writeValue(text) } Button { - id: writeButton - visible: node.writable + visible: node.writable && !currentPage.indexRange text: "Write" onClicked: node.writeValue(editField.text) } + + // Index range write controls + TextField { + id: rangeField + visible: node.writable && currentPage.indexRange === true + Layout.preferredWidth: 60 + placeholderText: "Range" + } + TextField { + id: rangeValueField + visible: node.writable && currentPage.indexRange === true + Layout.fillWidth: true + placeholderText: "Value(s)..." + onAccepted: node.writeValueAtRange(text, rangeField.text) + } + Button { + visible: node.writable && currentPage.indexRange === true + text: "Write Range" + onClicked: node.writeValueAtRange(rangeValueField.text, rangeField.text) + } + Label { visible: !node.writable text: "(READ-ONLY)" diff --git a/src/OpcUaMonitoredNode.cpp b/src/OpcUaMonitoredNode.cpp index cd93730..922ad43 100644 --- a/src/OpcUaMonitoredNode.cpp +++ b/src/OpcUaMonitoredNode.cpp @@ -177,6 +177,7 @@ OpcUaMonitoredNode::teardownNode () emit infoChanged (); m_valueType = QOpcUa::Types::Undefined; + m_pendingRangeWrite = false; if (m_writable) { @@ -270,6 +271,9 @@ OpcUaMonitoredNode::handleAttributeUpdated (QOpcUa::NodeAttribute attr, void OpcUaMonitoredNode::handleValueUpdated (const QVariant &value) { + if (m_pendingRangeWrite) + return; + m_value = value; emit valueChanged (); @@ -306,6 +310,8 @@ OpcUaMonitoredNode::handleAttributeWritten (QOpcUa::NodeAttribute attr, if (attr != QOpcUa::NodeAttribute::Value) return; + m_pendingRangeWrite = false; + bool ok = (statusCode == QOpcUa::Good); emit writeCompleted (ok, ok ? QStringLiteral ("Write successful") : QOpcUa::statusToString (statusCode)); @@ -511,3 +517,64 @@ OpcUaMonitoredNode::writeValue (const QVariant &value) emit writeCompleted (false, QStringLiteral ("Write request failed to dispatch")); } + +void +OpcUaMonitoredNode::writeValueAtRange (const QVariant &value, + const QString &indexRange) +{ + if (!m_node) + { + emit writeCompleted (false, QStringLiteral ("Node not connected")); + return; + } + if (!m_writable) + { + emit writeCompleted (false, QStringLiteral ("Node is read-only")); + return; + } + if (m_valueType == QOpcUa::Types::Undefined) + { + emit writeCompleted (false, + QStringLiteral ("Data type not yet resolved")); + return; + } + if (indexRange.isEmpty ()) + { + emit writeCompleted (false, QStringLiteral ("Index range is empty")); + return; + } + + QVariant toWrite = value; + + // If the input is a comma-separated string, split into a list so + // coerceValue can handle each element (e.g. "10, 20" for range "0:1"). + if (value.metaType ().id () == QMetaType::QString + && value.toString ().contains (QLatin1Char (','))) + { + QStringList parts + = value.toString ().split (QLatin1Char (','), Qt::SkipEmptyParts); + QVariantList list; + list.reserve (parts.size ()); + for (const QString &part : parts) + list.append (QVariant (part.trimmed ())); + toWrite = QVariant::fromValue (list); + } + + QVariant coerced = coerceValue (toWrite); + if (!coerced.isValid ()) + { + emit writeCompleted ( + false, QStringLiteral ("Cannot convert value to node data type")); + return; + } + + m_pendingRangeWrite = true; + + if (!m_node->writeAttributeRange (QOpcUa::NodeAttribute::Value, coerced, + indexRange, m_valueType)) + { + m_pendingRangeWrite = false; + emit writeCompleted ( + false, QStringLiteral ("Write range request failed to dispatch")); + } +} diff --git a/src/OpcUaMonitoredNode.h b/src/OpcUaMonitoredNode.h index 2e86b2c..2ecffb7 100644 --- a/src/OpcUaMonitoredNode.h +++ b/src/OpcUaMonitoredNode.h @@ -88,6 +88,18 @@ public: */ Q_INVOKABLE void writeValue (const QVariant &value); + /** + * @brief Write a value to a specific index range of an array node. + * + * @a indexRange uses OPC UA syntax: "0" for first element, + * "0:3" for elements 0–3, "0,1" for 2D indexing. + * For multi-element writes, @a value can be a comma-separated + * string or a QVariantList. + * Result is reported asynchronously via writeCompleted(). + */ + Q_INVOKABLE void writeValueAtRange (const QVariant &value, + const QString &indexRange); + void classBegin () override; void componentComplete () override; @@ -129,6 +141,7 @@ private: bool m_writable = false; QVariant m_value; OpcUaNodeInfo m_info; + bool m_pendingRangeWrite = false; }; #endif // OPCUAMONITOREDNODE_H -- cgit v1.2.3