1// Copyright (C) 2018 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include <private/opcuanode_p.h>
5#include <private/opcuarelativenodepath_p.h>
6#include <private/opcuarelativenodeid_p.h>
7#include <private/opcuapathresolver_p.h>
8#include <private/opcuaattributevalue_p.h>
9
10#include <qopcuatype.h>
11#include <QOpcUaNode>
12#include <QOpcUaClient>
13#include <QLoggingCategory>
14
15QT_BEGIN_NAMESPACE
16
17/*!
18 \qmltype Node
19 \inqmlmodule QtOpcUa
20 \brief Represents a node on a server.
21 \since QtOpcUa 5.12
22 \deprecated [6.9]
23
24 \code
25 import QtOpcUa as QtOpcUa
26
27 QtOpcUa.Node {
28 nodeId : QtOpcUa.NodeId {
29 identifier: "s=Example.Method"
30 ns: "Example Namespace"
31 }
32 connection: myConnection
33 }
34 \endcode
35*/
36
37/*!
38 \qmlproperty NodeId Node::nodeId
39
40 ID of the node on the server to be used.
41 This can be an absolute node ID or a relative node ID.
42
43 \sa NodeId, RelativeNodeId
44*/
45
46/*!
47 \qmlproperty Connection Node::connection
48
49 The connection to be used for node instances.
50 The node will automatically be accessible when the associated connection
51 has established a connection to a server.
52
53 If this property is not set, the default connection will be used, if any.
54
55 \sa Connection, Connection::defaultConnection
56*/
57
58/*!
59 \qmlproperty bool Node::readyToUse
60 \readonly
61
62 This property returns whether the node is ready to use.
63 This happens once after a successful connection to a server was established
64 and the node was successfully set up.
65*/
66
67/*!
68 \qmlsignal Connection::nodeChanged()
69
70 Emitted when the underlying node has changed.
71 This happens when the namespace or identifier of the \l NodeId changed.
72*/
73
74/*!
75 \qmlproperty QOpcUa::NodeClass Node::nodeClass
76 \readonly
77
78 The node class of the node. In case the information is not available
79 \c QtOpcUa.Constants.NodeClass.Undefined is returned.
80*/
81
82/*!
83 \qmlproperty string Node::browseName
84
85 The browse name of the node. In case the information is not available
86 an empty string is returned.
87*/
88
89/*!
90 \qmlproperty LocalizedText Node::displayName
91
92 The localized text of the node. In case the information is not available
93 a default constructed \l LocalizedText is returned.
94*/
95
96/*!
97 \qmlproperty LocalizedText Node::description
98
99 The description of the node. In case the information is not available
100 a default constructed \l LocalizedText is returned.
101*/
102
103/*!
104 \qmlproperty Status Node::status
105 \readonly
106
107 Current status of the node. The node is only usable when the status is \c Valid.
108 In any other case it indicates an error.
109
110 \sa errorMessage, Status
111*/
112
113/*!
114 \qmlproperty string Node::errorMessage
115 \readonly
116
117 A string representation of the current status code.
118
119 \sa status, Status
120*/
121
122/*!
123 \qmlproperty enumeration Node::Status
124
125 Status of a node.
126
127 \value Node.Status.Valid Node is ready for use
128 \value Node.Status.InvalidNodeId Node id is invalid
129 \value Node.Status.NoConnection Not connected to a server
130 \value Node.Status.InvalidNodeType Node type on the server does not match the QML type
131 \value Node.Status.InvalidClient Invalid connection client
132 \value Node.Status.FailedToResolveNode Failed to resolve node
133 \value Node.Status.InvalidObjectNode The object node is invalid or its type does not match the expected type
134 \value Node.Status.FailedToReadAttributes Failed to read attributes
135 \value Node.Status.FailedToSetupMonitoring Failed to setup monitoring
136
137 \sa status, errorMessage
138*/
139
140/*!
141 \qmlproperty EventFilter Node::eventFilter
142 \since 5.13
143
144 An event filter allows monitoring events on the server for certain conditions.
145
146 \sa EventFilter
147*/
148
149Q_LOGGING_CATEGORY(QT_OPCUA_PLUGINS_QML, "qt.opcua.plugins.qml")
150
151OpcUaNode::OpcUaNode(QObject *parent):
152 QObject(parent),
153 m_nodeId(new OpcUaNodeIdType(this)),
154 m_attributesToRead(QOpcUaNode::mandatoryBaseAttributes()),
155 m_status(Status::InvalidNodeId)
156{
157 m_attributesToRead |= QOpcUa::NodeAttribute::Description;
158 connect(sender: &m_resolvedNode, signal: &UniversalNode::nodeChanged, context: this, slot: &OpcUaNode::nodeChanged);
159 connect(sender: m_attributeCache.attribute(attribute: QOpcUa::NodeAttribute::BrowseName), signal: &OpcUaAttributeValue::changed, context: this, slot: &OpcUaNode::browseNameChanged);
160 connect(sender: m_attributeCache.attribute(attribute: QOpcUa::NodeAttribute::NodeClass), signal: &OpcUaAttributeValue::changed, context: this, slot: &OpcUaNode::nodeClassChanged);
161 connect(sender: m_attributeCache.attribute(attribute: QOpcUa::NodeAttribute::DisplayName), signal: &OpcUaAttributeValue::changed, context: this, slot: &OpcUaNode::displayNameChanged);
162 connect(sender: m_attributeCache.attribute(attribute: QOpcUa::NodeAttribute::Description), signal: &OpcUaAttributeValue::changed, context: this, slot: &OpcUaNode::descriptionChanged);
163}
164
165OpcUaNode::~OpcUaNode()
166{
167 delete m_node;
168}
169
170OpcUaNodeIdType *OpcUaNode::nodeId() const
171{
172 return m_nodeId;
173}
174
175OpcUaConnection *OpcUaNode::connection()
176{
177 if (!m_connection)
178 setConnection(OpcUaConnection::defaultConnection());
179
180 return m_connection;
181}
182
183bool OpcUaNode::readyToUse() const
184{
185 return m_readyToUse;
186}
187
188void OpcUaNode::setBrowseName(const QString &value)
189{
190 if (!m_connection || !m_node)
191 return;
192 if (!m_resolvedNode.isNamespaceIndexValid())
193 return;
194
195 m_node->writeAttribute(attribute: QOpcUa::NodeAttribute::BrowseName, value: QOpcUaQualifiedName(m_resolvedNode.namespaceIndex(), value));
196}
197
198QString OpcUaNode::browseName()
199{
200 return m_attributeCache.attributeValue(QOpcUa::NodeAttribute::BrowseName).value<QOpcUaQualifiedName>().name();
201}
202
203QOpcUa::NodeClass OpcUaNode::nodeClass()
204{
205 return m_attributeCache.attributeValue(QOpcUa::NodeAttribute::NodeClass).value<QOpcUa::NodeClass>();
206}
207
208void OpcUaNode::setDisplayName(const QOpcUaLocalizedText &value)
209{
210 if (!m_connection || !m_node)
211 return;
212 m_node->writeAttribute(attribute: QOpcUa::NodeAttribute::DisplayName, value);
213}
214
215QOpcUaLocalizedText OpcUaNode::displayName()
216{
217 return m_attributeCache.attributeValue(QOpcUa::NodeAttribute::DisplayName).value<QOpcUaLocalizedText>();
218}
219
220void OpcUaNode::setDescription(const QOpcUaLocalizedText &value)
221{
222 if (!m_connection || !m_node)
223 return;
224 m_node->writeAttribute(attribute: QOpcUa::NodeAttribute::Description, value);
225}
226
227QOpcUaLocalizedText OpcUaNode::description()
228{
229 return m_attributeCache.attributeValue(QOpcUa::NodeAttribute::Description).value<QOpcUaLocalizedText>();
230}
231
232OpcUaNode::Status OpcUaNode::status() const
233{
234 return m_status;
235}
236
237const QString &OpcUaNode::errorMessage() const
238{
239 return m_errorMessage;
240}
241
242/*!
243 \qmlmethod Date Node::getSourceTimestamp(QOpcUa::NodeAttribute attribute)
244
245 Returns the source timestamp of the given \a attribute.
246*/
247QDateTime OpcUaNode::getSourceTimestamp(QOpcUa::NodeAttribute attribute) const
248{
249 if (!m_connection || !m_node)
250 return QDateTime();
251 return m_node->sourceTimestamp(attribute);
252}
253
254/*!
255 \qmlmethod Date Node::getServerTimestamp(Constants::NodeAttribute attribute)
256
257 Returns the server timestamp of the given \a attribute.
258*/
259QDateTime OpcUaNode::getServerTimestamp(QOpcUa::NodeAttribute attribute) const
260{
261 if (!m_connection || !m_node)
262 return QDateTime();
263 return m_node->serverTimestamp(attribute);
264}
265
266void OpcUaNode::setNodeId(OpcUaNodeIdType *nodeId)
267{
268 if (m_nodeId == nodeId)
269 return;
270
271 // This deletes the initial dummy object that was created in the
272 // constructor in case a "real" nodeId is set.
273 if (m_nodeId->parent() == this)
274 m_nodeId->deleteLater();
275
276 // Disconnect signals from old node
277 m_nodeId->disconnect(receiver: this);
278 m_nodeId = nodeId;
279 connect(sender: m_nodeId, signal: &OpcUaNodeIdType::nodeChanged, context: this, slot: &OpcUaNode::updateNode);
280 connect(sender: m_nodeId, signal: &OpcUaNodeIdType::destroyed, context: this, slot: [&]() { m_nodeId = nullptr; });
281
282 updateNode();
283}
284
285void OpcUaNode::setConnection(OpcUaConnection *connection)
286{
287 if (connection == m_connection)
288 return;
289
290 m_connection = connection;
291 connect(sender: connection, SIGNAL(connectedChanged()), receiver: this, SLOT(updateNode()));
292
293 updateNode();
294 emit connectionChanged(connection);
295}
296
297void OpcUaNode::setupNode(const QString &absoluteNodePath)
298{
299 m_attributeCache.invalidate();
300 m_absoluteNodePath = absoluteNodePath;
301
302 if (m_node) {
303 m_node->deleteLater();
304
305 // Prevents a race condition where an update from the old node appears after the cache has been invalidate
306 QObject::disconnect(m_attributeUpdatedConnection);
307 QObject::disconnect(m_attributeReadConnection);
308 QObject::disconnect(m_enableMonitoringFinishedConnection);
309 QObject::disconnect(m_disableMonitoringFinishedConnection);
310 QObject::disconnect(m_monitoringStatusChangedConnection);
311 QObject::disconnect(m_eventOccurredConnection);
312
313 m_node = nullptr;
314 }
315
316 if (m_absoluteNodePath.isEmpty())
317 return;
318
319 auto conn = connection();
320 if (!conn || !m_nodeId || !conn->m_client)
321 return;
322
323 if (!conn->connected())
324 return;
325
326 m_node = conn->m_client->node(nodeId: m_absoluteNodePath);
327 if (!m_node) {
328 qCWarning(QT_OPCUA_PLUGINS_QML) << "Invalid node:" << m_absoluteNodePath;
329 return;
330 }
331
332 m_attributeUpdatedConnection = connect(sender: m_node, signal: &QOpcUaNode::attributeUpdated,
333 context: &m_attributeCache, slot: &OpcUaAttributeCache::setAttributeValue);
334
335 m_attributeReadConnection = connect(sender: m_node, signal: &QOpcUaNode::attributeRead, context: this, slot: [this](){
336 setReadyToUse(true);
337 });
338
339 m_enableMonitoringFinishedConnection = connect(sender: m_node, signal: &QOpcUaNode::enableMonitoringFinished, context: this,
340 slot: [this](QOpcUa::NodeAttribute attr, QOpcUa::UaStatusCode statusCode){
341 if (attr != QOpcUa::NodeAttribute::EventNotifier)
342 return;
343 if (statusCode == QOpcUa::Good) {
344 m_eventFilterActive = true;
345 qCDebug(QT_OPCUA_PLUGINS_QML) << "Event filter was enabled for node" << resolvedNode().fullNodeId();
346 updateEventFilter();
347 } else {
348 qCWarning(QT_OPCUA_PLUGINS_QML) << "Failed to enable event filter for node" << resolvedNode().fullNodeId();
349 setStatus(status: Status::FailedToSetupMonitoring);
350 }
351 });
352
353 m_disableMonitoringFinishedConnection = connect(sender: m_node, signal: &QOpcUaNode::disableMonitoringFinished, context: this,
354 slot: [this](QOpcUa::NodeAttribute attr, QOpcUa::UaStatusCode statusCode){
355 if (attr != QOpcUa::NodeAttribute::EventNotifier)
356 return;
357 if (statusCode == QOpcUa::Good) {
358 m_eventFilterActive = false;
359 qCDebug(QT_OPCUA_PLUGINS_QML) << "Event filter was disabled for node "<< resolvedNode().fullNodeId();
360 } else {
361 qCWarning(QT_OPCUA_PLUGINS_QML) << "Failed to disable event filter for node "<< resolvedNode().fullNodeId();
362 setStatus(status: Status::FailedToDisableMonitoring);
363 }
364 });
365
366 m_monitoringStatusChangedConnection = connect(sender: m_node, signal: &QOpcUaNode::monitoringStatusChanged, context: this,
367 slot: [this](QOpcUa::NodeAttribute attr, QOpcUaMonitoringParameters::Parameters items,
368 QOpcUa::UaStatusCode statusCode) {
369 Q_UNUSED(items);
370 if (attr != QOpcUa::NodeAttribute::EventNotifier)
371 return;
372 if (statusCode != QOpcUa::Good) {
373 setStatus(status: Status::FailedToModifyMonitoring);
374 qCWarning(QT_OPCUA_PLUGINS_QML) << "Failed to modify event filter for" << m_node->nodeId();
375 }
376 });
377
378 m_eventOccurredConnection = connect (sender: m_node, signal: &QOpcUaNode::eventOccurred, context: this, slot: &OpcUaNode::eventOccurred);
379
380
381 // Read mandatory attributes
382 if (!m_node->readAttributes(attributes: m_attributesToRead)) {
383 qCWarning(QT_OPCUA_PLUGINS_QML) << "Reading attributes" << m_node->nodeId() << "failed";
384 setStatus(status: Status::FailedToReadAttributes);
385 }
386
387 updateEventFilter();
388}
389
390void OpcUaNode::updateNode()
391{
392 retrieveAbsoluteNodePath(m_nodeId, [this](const QString &absoluteNodePath) {setupNode(absoluteNodePath);});
393}
394
395OpcUaEventFilter *OpcUaNode::eventFilter() const
396{
397 return m_eventFilter;
398}
399
400void OpcUaNode::setEventFilter(OpcUaEventFilter *eventFilter)
401{
402 bool changed = false;
403
404 if (m_eventFilter) {
405 disconnect(sender: m_eventFilter, signal: &OpcUaEventFilter::dataChanged, receiver: this, slot: &OpcUaNode::updateEventFilter);
406 changed = !(*m_eventFilter == *eventFilter);
407 } else {
408 changed = true;
409 }
410
411 m_eventFilter = eventFilter;
412 connect(sender: m_eventFilter, signal: &OpcUaEventFilter::dataChanged, context: this, slot: &OpcUaNode::updateEventFilter);
413
414 if (changed)
415 emit eventFilterChanged();
416}
417
418
419void OpcUaNode::updateEventFilter()
420{
421 if (!m_connection || !m_node || !m_eventFilter)
422 return;
423
424 if (m_eventFilterActive) {
425 m_node->modifyEventFilter(eventFilter: m_eventFilter->filter(client: m_connection->m_client));
426 } else {
427 QOpcUaMonitoringParameters parameters;
428 parameters.setFilter(m_eventFilter->filter(client: m_connection->m_client));
429 m_node->enableMonitoring(attr: QOpcUa::NodeAttribute::EventNotifier, settings: parameters);
430 m_eventFilterActive = true;
431 }
432}
433
434void OpcUaNode::setStatus(OpcUaNode::Status status, const QString &message)
435{
436 QString errorMessage(message);
437 bool emitStatusChanged = false;
438 bool emitErrorMessageChanged = false;
439
440 if (m_status != status) {
441 m_status = status;
442 emitStatusChanged = true;
443 }
444
445 // if error message is not given, use default error message
446 if (errorMessage.isEmpty()) {
447 switch (m_status) {
448 case Status::Valid:
449 errorMessage = tr(s: "Node is valid");
450 break;
451 case Status::InvalidNodeId:
452 errorMessage = tr(s: "Node Id is invalid");
453 break;
454 case Status::NoConnection:
455 errorMessage = tr(s: "Not connected to server");
456 break;
457 case Status::InvalidNodeType:
458 errorMessage = tr(s: "QML element does not match node type on the server");
459 break;
460 case Status::InvalidClient:
461 errorMessage = tr(s: "Connecting client is invalid");
462 break;
463 case Status::FailedToResolveNode:
464 errorMessage = tr(s: "Failed to resolve node");
465 break;
466 case Status::InvalidObjectNode:
467 errorMessage = tr(s: "Invalid object node");
468 break;
469 case Status::FailedToReadAttributes:
470 errorMessage = tr(s: "Failed to read attributes");
471 break;
472 case Status::FailedToSetupMonitoring:
473 errorMessage = tr(s: "Failed to setup monitoring");
474 break;
475 case Status::FailedToWriteAttribute:
476 errorMessage = tr(s: "Failed to write attribute");
477 break;
478 case Status::FailedToModifyMonitoring:
479 errorMessage = tr(s: "Failed to modify monitoring");
480 break;
481 case Status::FailedToDisableMonitoring:
482 errorMessage = tr(s: "Failed to disable monitoring");
483 break;
484 }
485 }
486
487 if (errorMessage != m_errorMessage) {
488 m_errorMessage = errorMessage;
489 emitErrorMessageChanged = true;
490 }
491
492 if (emitStatusChanged)
493 emit statusChanged();
494 if (emitErrorMessageChanged)
495 emit errorMessageChanged();
496}
497
498const UniversalNode &OpcUaNode::resolvedNode() const
499{
500 return m_resolvedNode;
501}
502
503QOpcUaNode *OpcUaNode::node() const
504{
505 return m_node;
506}
507
508void OpcUaNode::setAttributesToRead(QOpcUa::NodeAttributes attributes)
509{
510 m_attributesToRead = attributes;
511}
512
513QOpcUa::NodeAttributes OpcUaNode::attributesToRead() const
514{
515 return m_attributesToRead;
516}
517
518void OpcUaNode::retrieveAbsoluteNodePath(OpcUaNodeIdType *node, std::function<void (const QString &)> functor)
519{
520 auto conn = connection();
521 if (!conn) {
522 qCWarning(QT_OPCUA_PLUGINS_QML) << "No connection to server";
523 setStatus(status: Status::NoConnection);
524 return;
525 }
526 if (!m_nodeId) {
527 qCWarning(QT_OPCUA_PLUGINS_QML) << "Invalid node ID";
528 setStatus(status: Status::InvalidNodeId);
529 return;
530 }
531 if (!conn->m_client) {
532 qCWarning(QT_OPCUA_PLUGINS_QML) << "Client instance is invalid";
533 setStatus(status: Status::InvalidClient);
534 return;
535 }
536
537 if (!conn->connected()) {
538 qCWarning(QT_OPCUA_PLUGINS_QML) << "not connected";
539 return;
540 }
541
542 if (qobject_cast<const OpcUaNodeId *>(object: node)) {
543 UniversalNode tmp(node);
544 tmp.resolveNamespace(client: conn->m_client);
545 m_resolvedNode.from(tmp);
546 functor(m_resolvedNode.fullNodeId());
547 emit nodeIdChanged(nodeId: m_nodeId);
548 emit nodeChanged();
549 } else if (qobject_cast<OpcUaRelativeNodeId *>(object: node)) {
550 auto nodeId = qobject_cast<OpcUaRelativeNodeId *>(object: node);
551 OpcUaPathResolver *resolver = new OpcUaPathResolver(nodeId, conn->m_client, this);
552 connect(sender: resolver, signal: &OpcUaPathResolver::resolvedNode, context: this, slot: [this, functor, resolver](UniversalNode nodeToUse, const QString &errorMessage) {
553 resolver->deleteLater();
554
555 if (!errorMessage.isEmpty()) {
556 qCWarning(QT_OPCUA_PLUGINS_QML) << "Failed to resolve node:" << errorMessage;
557 setStatus(status: Status::FailedToResolveNode, message: errorMessage);
558 functor(QString());
559 return;
560 }
561
562 m_resolvedNode.from(nodeToUse);
563 functor(m_resolvedNode.fullNodeId());
564 emit nodeIdChanged(nodeId: m_nodeId);
565 emit nodeChanged();
566 });
567 resolver->startResolving();
568 } else {
569 functor(QString());
570 }
571}
572
573void OpcUaNode::setReadyToUse(bool value)
574{
575 if (value && !checkValidity())
576 value = false;
577
578 bool old = m_readyToUse;
579 m_readyToUse = value;
580
581 if (value)
582 setStatus(status: Status::Valid);
583
584 if (!old && value)
585 emit readyToUseChanged();
586}
587
588bool OpcUaNode::checkValidity()
589{
590 return true;
591}
592
593QT_END_NAMESPACE
594

source code of qtopcua/src/declarative_opcua/opcuanode.cpp