diff options
| author | Thomas Vanbesien <tvanbesi@proton.me> | 2026-02-23 17:35:34 +0100 |
|---|---|---|
| committer | Thomas Vanbesien <tvanbesi@proton.me> | 2026-02-23 17:35:34 +0100 |
| commit | 4ac860baa81f30e3e1fc9aaa42a3f0bb0537543a (patch) | |
| tree | 3ba02ef1943b0a6fcba49d8066b28c629f2a47a9 | |
| parent | dea81e8c330d75eaaa96e7f1915a9824ca23b15d (diff) | |
| download | BobinkQtOpcUa-4ac860baa81f30e3e1fc9aaa42a3f0bb0537543a.tar.gz BobinkQtOpcUa-4ac860baa81f30e3e1fc9aaa42a3f0bb0537543a.zip | |
Add writeValueAtRange for index-range array writes
| -rw-r--r-- | demo/NodePage.qml | 41 | ||||
| -rw-r--r-- | src/OpcUaMonitoredNode.cpp | 67 | ||||
| -rw-r--r-- | 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 @@ -91,6 +91,19 @@ Page { ] }, { + 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." + " The row should show no value and no metadata in the tooltip.", @@ -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 |
