summaryrefslogtreecommitdiffstats
path: root/src/OpcUaMonitoredNode.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/OpcUaMonitoredNode.cpp')
-rw-r--r--src/OpcUaMonitoredNode.cpp220
1 files changed, 216 insertions, 4 deletions
diff --git a/src/OpcUaMonitoredNode.cpp b/src/OpcUaMonitoredNode.cpp
index b158881..518ddd5 100644
--- a/src/OpcUaMonitoredNode.cpp
+++ b/src/OpcUaMonitoredNode.cpp
@@ -33,7 +33,8 @@ OpcUaMonitoredNode::setNodeId (const QString &id)
return;
teardownNode ();
- if (OpcUaClient::instance () && OpcUaClient::instance ()->connected ())
+ auto *client = OpcUaClient::instance ();
+ if (client && client->connected ())
setupNode ();
}
@@ -58,6 +59,12 @@ OpcUaMonitoredNode::value () const
return m_value;
}
+bool
+OpcUaMonitoredNode::writable () const
+{
+ return m_writable;
+}
+
OpcUaNodeInfo
OpcUaMonitoredNode::info () const
{
@@ -113,6 +120,8 @@ OpcUaMonitoredNode::setupNode ()
&OpcUaMonitoredNode::handleAttributeUpdated);
connect (m_node, &QOpcUaNode::valueAttributeUpdated, this,
&OpcUaMonitoredNode::handleValueUpdated);
+ connect (m_node, &QOpcUaNode::attributeWritten, this,
+ &OpcUaMonitoredNode::handleAttributeWritten);
m_node->readAttributes (
QOpcUa::NodeAttribute::Value | QOpcUa::NodeAttribute::DisplayName
@@ -134,6 +143,14 @@ OpcUaMonitoredNode::teardownNode ()
m_info = OpcUaNodeInfo{};
emit infoChanged ();
+
+ m_valueType = QOpcUa::Types::Undefined;
+
+ if (m_writable)
+ {
+ m_writable = false;
+ emit writableChanged ();
+ }
}
/* ======================================
@@ -163,15 +180,24 @@ OpcUaMonitoredNode::handleAttributeUpdated (QOpcUa::NodeAttribute attr,
break;
case QOpcUa::NodeAttribute::DataType:
{
- auto ns0 = QOpcUa::namespace0IdFromNodeId (value.toString ());
+ const QString rawId = value.toString ();
+ m_valueType = QOpcUa::opcUaDataTypeToQOpcUaType (rawId);
+ auto ns0 = QOpcUa::namespace0IdFromNodeId (rawId);
m_info.dataType = QOpcUa::namespace0IdName (ns0);
if (m_info.dataType.isEmpty ())
- m_info.dataType = value.toString ();
+ m_info.dataType = rawId;
}
break;
case QOpcUa::NodeAttribute::AccessLevel:
{
quint32 bits = value.toUInt ();
+ bool newWritable
+ = (bits & quint32 (QOpcUa::AccessLevelBit::CurrentWrite)) != 0;
+ if (m_writable != newWritable)
+ {
+ m_writable = newWritable;
+ emit writableChanged ();
+ }
QStringList flags;
if (bits & quint32 (QOpcUa::AccessLevelBit::CurrentRead))
flags << QStringLiteral ("Read");
@@ -187,7 +213,7 @@ OpcUaMonitoredNode::handleAttributeUpdated (QOpcUa::NodeAttribute attr,
}
break;
default:
- return;
+ return; // Skip infoChanged for attributes we don't track.
}
emit infoChanged ();
}
@@ -223,3 +249,189 @@ OpcUaMonitoredNode::handleClientConnectedChanged ()
teardownNode ();
}
}
+
+void
+OpcUaMonitoredNode::handleAttributeWritten (QOpcUa::NodeAttribute attr,
+ QOpcUa::UaStatusCode statusCode)
+{
+ if (attr != QOpcUa::NodeAttribute::Value)
+ return;
+
+ bool ok = (statusCode == QOpcUa::Good);
+ emit writeCompleted (ok, ok ? QStringLiteral ("Write successful")
+ : QOpcUa::statusToString (statusCode));
+}
+
+/* ======================================
+ * Write support
+ * ====================================== */
+
+/**
+ * @brief Convert a QML value to the C++ type matching m_valueType.
+ *
+ * Returns an invalid QVariant if conversion fails (e.g. non-numeric
+ * string for an integer node, or out-of-range for narrow types).
+ * Arrays are handled by recursing over each element.
+ */
+QVariant
+OpcUaMonitoredNode::coerceValue (const QVariant &input) const
+{
+ if (input.metaType ().id () == QMetaType::QVariantList)
+ {
+ QVariantList list = input.toList ();
+ QVariantList result;
+ result.reserve (list.size ());
+ for (const QVariant &item : list)
+ {
+ QVariant coerced = coerceValue (item);
+ if (!coerced.isValid ())
+ return {};
+ result.append (coerced);
+ }
+ return QVariant::fromValue (result);
+ }
+
+ switch (m_valueType)
+ {
+ case QOpcUa::Boolean:
+ {
+ if (input.metaType ().id () == QMetaType::Bool)
+ return QVariant (input.toBool ());
+ QString s = input.toString ().trimmed ().toLower ();
+ if (s == QStringLiteral ("true") || s == QStringLiteral ("1"))
+ return QVariant (true);
+ if (s == QStringLiteral ("false") || s == QStringLiteral ("0"))
+ return QVariant (false);
+ return {};
+ }
+ case QOpcUa::SByte:
+ {
+ bool ok = false;
+ qint32 v = input.toInt (&ok);
+ if (!ok || v < -128 || v > 127)
+ return {};
+ return QVariant::fromValue (static_cast<qint8> (v));
+ }
+ case QOpcUa::Byte:
+ {
+ bool ok = false;
+ quint32 v = input.toUInt (&ok);
+ if (!ok || v > 255)
+ return {};
+ return QVariant::fromValue (static_cast<quint8> (v));
+ }
+ case QOpcUa::Int16:
+ {
+ bool ok = false;
+ qint32 v = input.toInt (&ok);
+ if (!ok || v < -32768 || v > 32767)
+ return {};
+ return QVariant::fromValue (static_cast<qint16> (v));
+ }
+ case QOpcUa::UInt16:
+ {
+ bool ok = false;
+ quint32 v = input.toUInt (&ok);
+ if (!ok || v > 65535)
+ return {};
+ return QVariant::fromValue (static_cast<quint16> (v));
+ }
+ case QOpcUa::Int32:
+ {
+ bool ok = false;
+ qint32 v = input.toInt (&ok);
+ return ok ? QVariant::fromValue (v) : QVariant{};
+ }
+ case QOpcUa::UInt32:
+ {
+ bool ok = false;
+ quint32 v = input.toUInt (&ok);
+ return ok ? QVariant::fromValue (v) : QVariant{};
+ }
+ case QOpcUa::Int64:
+ {
+ bool ok = false;
+ qint64 v = input.toLongLong (&ok);
+ return ok ? QVariant::fromValue (v) : QVariant{};
+ }
+ case QOpcUa::UInt64:
+ {
+ bool ok = false;
+ quint64 v = input.toULongLong (&ok);
+ return ok ? QVariant::fromValue (v) : QVariant{};
+ }
+ case QOpcUa::Float:
+ {
+ bool ok = false;
+ float v = input.toFloat (&ok);
+ return ok ? QVariant::fromValue (v) : QVariant{};
+ }
+ case QOpcUa::Double:
+ {
+ bool ok = false;
+ double v = input.toDouble (&ok);
+ return ok ? QVariant::fromValue (v) : QVariant{};
+ }
+ case QOpcUa::String:
+ return QVariant (input.toString ());
+ case QOpcUa::DateTime:
+ {
+ QDateTime dt = input.toDateTime ();
+ return dt.isValid () ? QVariant::fromValue (dt) : QVariant{};
+ }
+ case QOpcUa::ByteString:
+ return QVariant::fromValue (input.toByteArray ());
+ default:
+ return input;
+ }
+}
+
+void
+OpcUaMonitoredNode::writeValue (const QVariant &value)
+{
+ 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;
+ }
+
+ QVariant toWrite = value;
+
+ // QML text fields always send QString. For array nodes (current
+ // value is QVariantList) split "1, 2, 3" into a list first so
+ // coerceValue can handle each element individually.
+ if (m_value.metaType ().id () == QMetaType::QVariantList
+ && value.metaType ().id () == QMetaType::QString)
+ {
+ 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;
+ }
+
+ if (!m_node->writeValueAttribute (coerced, m_valueType))
+ emit writeCompleted (false,
+ QStringLiteral ("Write request failed to dispatch"));
+}