summaryrefslogtreecommitdiffstats
path: root/src/BobinkNode.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/BobinkNode.cpp')
-rw-r--r--src/BobinkNode.cpp297
1 files changed, 297 insertions, 0 deletions
diff --git a/src/BobinkNode.cpp b/src/BobinkNode.cpp
new file mode 100644
index 0000000..241b9c5
--- /dev/null
+++ b/src/BobinkNode.cpp
@@ -0,0 +1,297 @@
+/**
+ * @file BobinkNode.cpp
+ * @brief QML component representing a single OPC UA node.
+ */
+#include "BobinkNode.h"
+#include "BobinkClient.h"
+
+#include <QOpcUaClient>
+#include <QOpcUaMonitoringParameters>
+
+static constexpr double DEFAULT_PUBLISHING_INTERVAL = 250.0; // ms
+
+/* ------------------------------------------------------------------ */
+/* Construction / destruction */
+/* ------------------------------------------------------------------ */
+
+BobinkNode::BobinkNode (QQuickItem *parent) : QQuickItem (parent) {}
+
+BobinkNode::~BobinkNode () { stopMonitoring (); }
+
+/* ------------------------------------------------------------------ */
+/* Properties */
+/* ------------------------------------------------------------------ */
+
+QString
+BobinkNode::nodeId () const
+{
+ return m_nodeId;
+}
+
+void
+BobinkNode::setNodeId (const QString &id)
+{
+ if (m_nodeId == id)
+ return;
+ m_nodeId = id;
+ emit nodeIdChanged ();
+
+ if (m_componentComplete && isVisible ())
+ {
+ stopMonitoring ();
+ startMonitoring ();
+ }
+}
+
+QVariant
+BobinkNode::value () const
+{
+ return m_value;
+}
+
+void
+BobinkNode::setValue (const QVariant &value)
+{
+ if (m_opcuaNode)
+ m_opcuaNode->writeValueAttribute (value);
+}
+
+BobinkNode::NodeStatus
+BobinkNode::status () const
+{
+ return m_status;
+}
+
+QDateTime
+BobinkNode::sourceTimestamp () const
+{
+ return m_sourceTimestamp;
+}
+
+QDateTime
+BobinkNode::serverTimestamp () const
+{
+ return m_serverTimestamp;
+}
+
+/* ------------------------------------------------------------------ */
+/* readAttribute */
+/* ------------------------------------------------------------------ */
+
+void
+BobinkNode::readAttribute (const QString &attributeName)
+{
+ if (!m_opcuaNode)
+ return;
+
+ QOpcUa::NodeAttribute attr = attributeFromName (attributeName);
+ if (attr == QOpcUa::NodeAttribute::None)
+ return;
+
+ m_pendingReads.insert (attr, attributeName);
+ m_opcuaNode->readAttributes (attr);
+}
+
+/* ------------------------------------------------------------------ */
+/* QQuickItem lifecycle */
+/* ------------------------------------------------------------------ */
+
+void
+BobinkNode::componentComplete ()
+{
+ QQuickItem::componentComplete ();
+ m_componentComplete = true;
+
+ auto *client = BobinkClient::instance ();
+ if (client)
+ connect (client, &BobinkClient::connectedChanged, this,
+ &BobinkNode::handleClientConnectedChanged);
+
+ if (isVisible ())
+ startMonitoring ();
+}
+
+void
+BobinkNode::itemChange (ItemChange change, const ItemChangeData &data)
+{
+ QQuickItem::itemChange (change, data);
+
+ if (change == ItemVisibleHasChanged && m_componentComplete)
+ {
+ if (data.boolValue)
+ startMonitoring ();
+ else
+ stopMonitoring ();
+ }
+}
+
+/* ------------------------------------------------------------------ */
+/* Monitoring lifecycle */
+/* ------------------------------------------------------------------ */
+
+void
+BobinkNode::startMonitoring ()
+{
+ if (m_opcuaNode || m_nodeId.isEmpty ())
+ return;
+
+ auto *client = BobinkClient::instance ();
+ if (!client || !client->connected ())
+ return;
+
+ QOpcUaClient *opcua = client->opcuaClient ();
+ if (!opcua)
+ return;
+
+ m_opcuaNode = opcua->node (m_nodeId);
+ if (!m_opcuaNode)
+ return;
+
+ connect (m_opcuaNode, &QOpcUaNode::dataChangeOccurred, this,
+ &BobinkNode::handleDataChange);
+ connect (m_opcuaNode, &QOpcUaNode::attributeUpdated, this,
+ &BobinkNode::handleAttributeUpdated);
+ connect (m_opcuaNode, &QOpcUaNode::attributeWritten, this,
+ &BobinkNode::handleAttributeWritten);
+
+ QOpcUaMonitoringParameters params (DEFAULT_PUBLISHING_INTERVAL);
+ m_opcuaNode->enableMonitoring (QOpcUa::NodeAttribute::Value, params);
+}
+
+void
+BobinkNode::stopMonitoring ()
+{
+ if (!m_opcuaNode)
+ return;
+
+ m_pendingReads.clear ();
+ delete m_opcuaNode;
+ m_opcuaNode = nullptr;
+}
+
+/* ------------------------------------------------------------------ */
+/* Signal handlers */
+/* ------------------------------------------------------------------ */
+
+void
+BobinkNode::handleDataChange (QOpcUa::NodeAttribute attr, const QVariant &val)
+{
+ if (attr != QOpcUa::NodeAttribute::Value)
+ return;
+
+ if (m_value != val)
+ {
+ m_value = val;
+ emit valueChanged ();
+ }
+
+ NodeStatus newStatus = statusFromCode (m_opcuaNode->valueAttributeError ());
+ if (m_status != newStatus)
+ {
+ m_status = newStatus;
+ emit statusChanged ();
+ }
+
+ QDateTime srcTs
+ = m_opcuaNode->sourceTimestamp (QOpcUa::NodeAttribute::Value);
+ if (m_sourceTimestamp != srcTs)
+ {
+ m_sourceTimestamp = srcTs;
+ emit sourceTimestampChanged ();
+ }
+
+ QDateTime srvTs
+ = m_opcuaNode->serverTimestamp (QOpcUa::NodeAttribute::Value);
+ if (m_serverTimestamp != srvTs)
+ {
+ m_serverTimestamp = srvTs;
+ emit serverTimestampChanged ();
+ }
+}
+
+void
+BobinkNode::handleAttributeUpdated (QOpcUa::NodeAttribute attr,
+ const QVariant &val)
+{
+ auto it = m_pendingReads.find (attr);
+ if (it != m_pendingReads.end ())
+ {
+ emit attributeRead (it.value (), val);
+ m_pendingReads.erase (it);
+ }
+}
+
+void
+BobinkNode::handleAttributeWritten (QOpcUa::NodeAttribute attr,
+ QOpcUa::UaStatusCode statusCode)
+{
+ if (attr != QOpcUa::NodeAttribute::Value)
+ return;
+
+ if (statusCode != QOpcUa::UaStatusCode::Good)
+ emit writeError (QStringLiteral ("Write failed: 0x%1")
+ .arg (static_cast<quint32> (statusCode), 8, 16,
+ QLatin1Char ('0')));
+}
+
+void
+BobinkNode::handleClientConnectedChanged ()
+{
+ auto *client = BobinkClient::instance ();
+ if (!client)
+ return;
+
+ if (client->connected ())
+ {
+ if (m_componentComplete && isVisible ())
+ startMonitoring ();
+ }
+ else
+ {
+ stopMonitoring ();
+ }
+}
+
+/* ------------------------------------------------------------------ */
+/* Helpers */
+/* ------------------------------------------------------------------ */
+
+BobinkNode::NodeStatus
+BobinkNode::statusFromCode (QOpcUa::UaStatusCode code)
+{
+ quint32 severity = static_cast<quint32> (code) >> 30;
+ switch (severity)
+ {
+ case 0:
+ return Good;
+ case 1:
+ return Uncertain;
+ default:
+ return Bad;
+ }
+}
+
+QOpcUa::NodeAttribute
+BobinkNode::attributeFromName (const QString &name)
+{
+ static const QHash<QString, QOpcUa::NodeAttribute> map = {
+ { "NodeId", QOpcUa::NodeAttribute::NodeId },
+ { "NodeClass", QOpcUa::NodeAttribute::NodeClass },
+ { "BrowseName", QOpcUa::NodeAttribute::BrowseName },
+ { "DisplayName", QOpcUa::NodeAttribute::DisplayName },
+ { "Description", QOpcUa::NodeAttribute::Description },
+ { "Value", QOpcUa::NodeAttribute::Value },
+ { "DataType", QOpcUa::NodeAttribute::DataType },
+ { "ValueRank", QOpcUa::NodeAttribute::ValueRank },
+ { "ArrayDimensions", QOpcUa::NodeAttribute::ArrayDimensions },
+ { "AccessLevel", QOpcUa::NodeAttribute::AccessLevel },
+ { "UserAccessLevel", QOpcUa::NodeAttribute::UserAccessLevel },
+ { "MinimumSamplingInterval",
+ QOpcUa::NodeAttribute::MinimumSamplingInterval },
+ { "Historizing", QOpcUa::NodeAttribute::Historizing },
+ { "Executable", QOpcUa::NodeAttribute::Executable },
+ { "UserExecutable", QOpcUa::NodeAttribute::UserExecutable },
+ };
+
+ return map.value (name, QOpcUa::NodeAttribute::None);
+}