diff options
Diffstat (limited to 'src/OpcUaMonitoredNode.cpp')
| -rw-r--r-- | src/OpcUaMonitoredNode.cpp | 302 |
1 files changed, 295 insertions, 7 deletions
diff --git a/src/OpcUaMonitoredNode.cpp b/src/OpcUaMonitoredNode.cpp index b158881..cd93730 100644 --- a/src/OpcUaMonitoredNode.cpp +++ b/src/OpcUaMonitoredNode.cpp @@ -7,7 +7,9 @@ #include <QMetaEnum> #include <QOpcUaLocalizedText> +#include <QOpcUaMonitoringParameters> #include <QStringList> +#include <QUuid> OpcUaMonitoredNode::OpcUaMonitoredNode (QObject *parent) : QObject (parent) {} @@ -33,7 +35,8 @@ OpcUaMonitoredNode::setNodeId (const QString &id) return; teardownNode (); - if (OpcUaClient::instance () && OpcUaClient::instance ()->connected ()) + auto *client = OpcUaClient::instance (); + if (client && client->connected ()) setupNode (); } @@ -50,6 +53,29 @@ OpcUaMonitoredNode::setMonitored (bool monitored) return; m_monitored = monitored; emit monitoredChanged (); + + if (!m_componentComplete || !m_node) + return; + + if (m_monitored) + startMonitoring (); + else + stopMonitoring (); +} + +double +OpcUaMonitoredNode::publishingInterval () const +{ + return m_publishingInterval; +} + +void +OpcUaMonitoredNode::setPublishingInterval (double interval) +{ + if (qFuzzyCompare (m_publishingInterval, interval)) + return; + m_publishingInterval = interval; + emit publishingIntervalChanged (); } QVariant @@ -58,6 +84,12 @@ OpcUaMonitoredNode::value () const return m_value; } +bool +OpcUaMonitoredNode::writable () const +{ + return m_writable; +} + OpcUaNodeInfo OpcUaMonitoredNode::info () const { @@ -113,11 +145,20 @@ OpcUaMonitoredNode::setupNode () &OpcUaMonitoredNode::handleAttributeUpdated); connect (m_node, &QOpcUaNode::valueAttributeUpdated, this, &OpcUaMonitoredNode::handleValueUpdated); + connect (m_node, &QOpcUaNode::attributeWritten, this, + &OpcUaMonitoredNode::handleAttributeWritten); + connect (m_node, &QOpcUaNode::enableMonitoringFinished, this, + &OpcUaMonitoredNode::handleEnableMonitoringFinished); + connect (m_node, &QOpcUaNode::disableMonitoringFinished, this, + &OpcUaMonitoredNode::handleDisableMonitoringFinished); m_node->readAttributes ( - QOpcUa::NodeAttribute::Value | QOpcUa::NodeAttribute::DisplayName - | QOpcUa::NodeAttribute::Description | QOpcUa::NodeAttribute::NodeClass - | QOpcUa::NodeAttribute::DataType | QOpcUa::NodeAttribute::AccessLevel); + QOpcUa::NodeAttribute::DisplayName | QOpcUa::NodeAttribute::Description + | QOpcUa::NodeAttribute::NodeClass | QOpcUa::NodeAttribute::DataType + | QOpcUa::NodeAttribute::AccessLevel); + + if (m_monitored) + startMonitoring (); } void @@ -134,6 +175,31 @@ OpcUaMonitoredNode::teardownNode () m_info = OpcUaNodeInfo{}; emit infoChanged (); + + m_valueType = QOpcUa::Types::Undefined; + + if (m_writable) + { + m_writable = false; + emit writableChanged (); + } +} + +void +OpcUaMonitoredNode::startMonitoring () +{ + if (!m_node || !m_monitored) + return; + QOpcUaMonitoringParameters params (m_publishingInterval); + m_node->enableMonitoring (QOpcUa::NodeAttribute::Value, params); +} + +void +OpcUaMonitoredNode::stopMonitoring () +{ + if (!m_node) + return; + m_node->disableMonitoring (QOpcUa::NodeAttribute::Value); } /* ====================================== @@ -163,15 +229,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 +262,7 @@ OpcUaMonitoredNode::handleAttributeUpdated (QOpcUa::NodeAttribute attr, } break; default: - return; + return; // Skip infoChanged for attributes we don't track. } emit infoChanged (); } @@ -223,3 +298,216 @@ 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)); +} + +void +OpcUaMonitoredNode::handleEnableMonitoringFinished ( + QOpcUa::NodeAttribute attr, QOpcUa::UaStatusCode statusCode) +{ + if (attr != QOpcUa::NodeAttribute::Value) + return; + if (statusCode != QOpcUa::Good) + qWarning () << "OpcUaMonitoredNode: enableMonitoring failed for" + << m_nodeId << ":" << QOpcUa::statusToString (statusCode); +} + +void +OpcUaMonitoredNode::handleDisableMonitoringFinished ( + QOpcUa::NodeAttribute attr, QOpcUa::UaStatusCode statusCode) +{ + if (attr != QOpcUa::NodeAttribute::Value) + return; + if (statusCode != QOpcUa::Good) + qWarning () << "OpcUaMonitoredNode: disableMonitoring failed for" + << m_nodeId << ":" << 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 ()); + case QOpcUa::Guid: + { + QUuid uuid (input.toString ()); + return !uuid.isNull () ? QVariant::fromValue (uuid) : QVariant{}; + } + 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")); +} |
