1// Copyright (C) 2019 The Qt Company Ltd.
2// Copyright (C) 2015 basysKom GmbH, opensource@basyskom.com
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4
5#include "qopcuaclient.h"
6#include "qopcuaconnectionsettings.h"
7#include "qopcuaexpandednodeid.h"
8#include "qopcuaqualifiedname.h"
9
10#include <private/qopcuaclient_p.h>
11
12QT_BEGIN_NAMESPACE
13
14using namespace Qt::Literals::StringLiterals;
15
16/*!
17 \class QOpcUaClient
18 \inmodule QtOpcUa
19
20 \brief QOpcUaClient allows interaction with an OPC UA server.
21
22 \section1 QOpcUaClient
23
24 QOpcUaClient implements basic client capabilities to communicate with
25 OPC UA enabled devices and applications. This includes querying a discovery server
26 for known servers, requesting a list of endpoints from a server, connecting and
27 disconnecting.
28
29 After successfully connecting to a server, QOpcUaClient allows getting \l QOpcUaNode
30 objects which enable further interaction with nodes on the OPC UA server.
31 For operations that concern multiple nodes, QOpcUaClient offers an API which supports
32 reading multiple attributes of multiple nodes in a single request to the server.
33
34 QOpcUaClient also keeps a local copy of the server's namespace array which is created after
35 a successful connect. This information can be queried or updated while the connection lasts.
36 The copy of the namespace array is also used for the resolution of expanded node ids and the
37 creation of qualified names from a namespace URI.
38
39 \section1 Addressing Nodes
40
41 For an introduction to nodes and node ids, see \l QOpcUaNode.
42
43 \section1 Usage
44 Create a \l QOpcUaClient using \l QOpcUaProvider, request a list of endpoints from the server
45 using \l requestEndpoints and call \l connectToEndpoint() to connect to one of the available endpoints.
46 After the connection is established, a \l QOpcUaNode object for the root node is requested.
47 \code
48 QOpcUaProvider provider;
49 if (provider.availableBackends().isEmpty())
50 return;
51 QOpcUaClient *client = provider.createClient(provider.availableBackends()[0]);
52 if (!client)
53 return;
54 // Connect to the stateChanged signal. Compatible slots of QObjects can be used instead of a lambda.
55 QObject::connect(client, &QOpcUaClient::stateChanged, [client](QOpcUaClient::ClientState state) {
56 qDebug() << "Client state changed:" << state;
57 if (state == QOpcUaClient::ClientState::Connected) {
58 QOpcUaNode *node = client->node("ns=0;i=84");
59 if (node)
60 qDebug() << "A node object has been created";
61 }
62 });
63
64 QObject::connect(client, &QOpcUaClient::endpointsRequestFinished,
65 [client](QList<QOpcUaEndpointDescription> endpoints) {
66 qDebug() << "Endpoints returned:" << endpoints.count();
67 if (endpoints.size())
68 client->connectToEndpoint(endpoints.first()); // Connect to the first endpoint in the list
69 });
70
71 client->requestEndpoints(QUrl("opc.tcp://127.0.0.1:4840")); // Request a list of endpoints from the server
72 \endcode
73*/
74
75/*!
76 \enum QOpcUaClient::ClientState
77
78 This enum type specifies the connection state of the client.
79
80 \value Disconnected
81 The client is not connected to a server.
82 \value Connecting
83 The client is currently connecting to a server.
84 \value Connected
85 The client is connected to a server.
86 \value Closing
87 The client has been connected and requests a disconnect from the server.
88*/
89
90/*!
91 \enum QOpcUaClient::ClientError
92
93 This enum type specifies the current error state of the client.
94
95 \value NoError
96 No error occurred.
97 \value InvalidUrl
98 The url to connect to has been wrongly specified or a connection to this url failed.
99 \value AccessDenied
100 An attempt to connect to a server using username/password failed due to wrong credentials.
101 \value ConnectionError
102 An error occurred with the connection.
103 \value UnknownError
104 An unknown error occurred.
105 \value UnsupportedAuthenticationInformation
106 The given type or data of authentication information is not supported.
107 \value InvalidAuthenticationInformation
108 The provided authentication information is invalid
109 \value InvalidEndpointDescription
110 The endpoint description is invalid, e. g. because of an empty URL or no user identity tokens.
111 \value NoMatchingUserIdentityTokenFound
112 The selected endpoint doesn't support the requested token type or supported policies.
113 \value UnsupportedSecurityPolicy
114 The security policy for the endpoint is not supported.
115 \value InvalidPki
116 A certificate or key of the PKI could not be loaded or is invalid
117 \value CertificateUntrusted
118 The server certificate is untrusted
119*/
120
121/*!
122 \property QOpcUaClient::error
123 \brief Specifies the current error state of the client.
124*/
125
126/*!
127 \property QOpcUaClient::state
128 \brief Specifies the current connection state of the client.
129*/
130
131/*!
132 \fn QOpcUaClient::connected()
133
134 This signal is emitted when a connection has been established.
135*/
136
137/*!
138 \fn QOpcUaClient::disconnected()
139
140 This signal is emitted when a connection has been closed following to a close request.
141*/
142
143/*!
144 \fn QOpcUaClient::connectError(QOpcUaErrorState *errorState)
145 \since QtOpcUa 5.13
146
147 This signal is emitted when an error happened during connection establishment.
148 The parameter \a errorState contains information about the error.
149
150 In case of client side errors, these can be ignored by calling
151 \l QOpcUaErrorState::setIgnoreError on the object.
152
153 During execution of a slot connected to this signal the backend is stopped and
154 waits for all slots to return. This allows to pop up a user dialog to ask the
155 enduser for example if to trust an unknown certificate before the backend continues.
156 */
157
158/*!
159 \fn QOpcUaClient::passwordForPrivateKeyRequired(QString keyFilePath, QString *password, bool previousTryWasInvalid)
160 \since QtOpcUa 5.13
161
162 This signal is emitted when a password for an encrypted private key is required.
163 The parameter \a keyFilePath contains the file path to key which is used.
164 The parameter \a previousTryWasInvalid is true if a previous try to decrypt the key failed (aka invalid password).
165 The parameter \a password points to a QString that has to be filled with the actual password for the key.
166 In case the previous try failed it contains the previously used password.
167
168 During execution of a slot connected to this signal the backend is stopped and
169 waits for all slots to return. This allows to pop up a user dialog to ask the
170 enduser for the password.
171 */
172
173/*!
174 \fn void QOpcUaClient::namespaceArrayUpdated(QStringList namespaces)
175
176 This signal is emitted after an updateNamespaceArray operation has finished.
177 \a namespaces contains the content of the server's namespace table. The index
178 of an entry in \a namespaces corresponds to the namespace index used in the node id.
179
180 If the namespace array content stays the same after the update this signal is emitted nevertheless.
181
182 \sa namespaceArrayChanged() updateNamespaceArray()
183*/
184
185/*!
186 \fn void QOpcUaClient::namespaceArrayChanged(QStringList namespaces)
187
188 This signal is emitted after the namespace array has changed.
189 \a namespaces contains the content of the server's namespace table. The index
190 of an entry in \a namespaces corresponds to the namespace index used in the node id.
191
192 \sa namespaceArrayUpdated() updateNamespaceArray()
193*/
194
195/*!
196 \fn void QOpcUaClient::endpointsRequestFinished(QList<QOpcUaEndpointDescription> endpoints, QOpcUa::UaStatusCode statusCode, QUrl requestUrl)
197
198 This signal is emitted after a \l requestEndpoints() operation has finished.
199 \a statusCode contains the result of the operation. If the result is \l {QOpcUa::UaStatusCode} {Good},
200 \a endpoints contains the descriptions of all endpoints that are available on the server.
201 \a requestUrl contains the URL that was used in the \l requestEndpoints() call.
202*/
203
204/*!
205 \fn void QOpcUaClient::findServersFinished(QList<QOpcUaApplicationDescription> servers, QOpcUa::UaStatusCode statusCode, QUrl requestUrl);
206
207 This signal is emitted after a \l findServers() operation has finished.
208 \a statusCode contains the result of the operation. If the result is \l {QOpcUa::UaStatusCode} {Good},
209 \a servers contains the application descriptions of all servers known to the queried server that matched the filter criteria.
210 \a requestUrl contains the URL that was used in the \l findServers() call.
211*/
212
213/*!
214 \fn void QOpcUaClient::readNodeAttributesFinished(QList<QOpcUaReadResult> results, QOpcUa::UaStatusCode serviceResult)
215
216 This signal is emitted after a \l readNodeAttributes() operation has finished.
217
218 The elements in \a results have the same order as the elements in the request. For each requested element,
219 there is a value together with timestamps and the status code in \a results.
220 \a serviceResult contains the status code from the OPC UA Read service.
221
222 \sa readNodeAttributes() QOpcUaReadResult QOpcUaReadItem
223*/
224
225/*!
226 \fn void QOpcUaClient::writeNodeAttributesFinished(QList<QOpcUaWriteResult> results, QOpcUa::UaStatusCode serviceResult)
227
228 This signal is emitted after a \l writeNodeAttributes() operation has finished.
229
230 The elements in \a results have the same order as the elements in the write request.
231 They contain the value, timestamps and status code received from the server as well as the node id,
232 attribute and index range from the write item. This facilitates matching the result with the request.
233
234 \a serviceResult is the status code from the the OPC UA Write service. If \a serviceResult is not
235 \l {QOpcUa::UaStatusCode} {Good}, the entries in \a results also have an invalid status code and must
236 not be used.
237
238 \sa writeNodeAttributes() QOpcUaWriteResult
239*/
240
241/*!
242 \fn void QOpcUaClient::addNodeFinished(QOpcUaExpandedNodeId requestedNodeId, QString assignedNodeId, QOpcUa::UaStatusCode statusCode)
243
244 This signal is emitted after an \l addNode() operation has finished.
245 \a requestedNodeId is the requested node id from the \l addNode() call, \a assignedNodeId is the node id the server has assigned to the new node.
246 \a statusCode contains the result of the operation. If the result is \l {QOpcUa::UaStatusCode} {Bad}, \a assignedNodeId is empty and no node
247 has been added to the server's address space.
248*/
249
250/*!
251 \fn void QOpcUaClient::deleteNodeFinished(QString nodeId, QOpcUa::UaStatusCode statusCode)
252
253 This signal is emitted after a \l deleteNode() operation has finished.
254 \a nodeId is the node id from the \l deleteNode() call.
255 \a statusCode contains the result of the operation.
256*/
257
258/*!
259 \fn void QOpcUaClient::addReferenceFinished(QString sourceNodeId, QString referenceTypeId, QOpcUaExpandedNodeId targetNodeId, bool isForwardReference, QOpcUa::UaStatusCode statusCode)
260
261 This signal is emitted after an \l addReference() operation has finished.
262 \a sourceNodeId, \a referenceTypeId, \a targetNodeId and \a isForwardReference are the values from the \l addReference() call.
263 \a statusCode contains the result of the operation.
264*/
265
266/*!
267 \fn void QOpcUaClient::deleteReferenceFinished(QString sourceNodeId, QString referenceTypeId, QOpcUaExpandedNodeId targetNodeId, bool isForwardReference, QOpcUa::UaStatusCode statusCode)
268
269 This signal is emitted after a \l deleteReference() operation has finished.
270 \a sourceNodeId, \a referenceTypeId, \a targetNodeId and \a isForwardReference are the values from the \l deleteReference() call.
271 \a statusCode contains the result of the operation.
272*/
273
274/*!
275 \fn void QOpcUaClient::registerNodesFinished(const QStringList &nodesToRegister, const QStringList &registeredNodeIds, QOpcUa::UaStatusCode statusCode)
276 \since 6.7
277
278 This signal is emitted after a \l registerNodes() operation has finished.
279 \a nodesToRegister contains the node ids from the request for correlation purposes.
280 The node ids returned by the server are in \a registeredNodeIds and have the same ordering as the ids in the request.
281 \a statusCode indicates if the operation was successful.
282
283 \sa registerNodes()
284*/
285
286/*!
287 \fn void QOpcUaClient::unregisterNodesFinished(const QStringList &nodesToUnregister, QOpcUa::UaStatusCode statusCode)
288 \since 6.7
289
290 This signal is emitted after a \l unregisterNodes() operation has finished.
291 \a nodesToUnregister contains the node ids from the request for correlation purposes.
292 \a statusCode indicates if the operation was successful.
293
294 \sa unregisterNodes()
295*/
296
297/*!
298 \internal QOpcUaClientImpl is an opaque type (as seen from the public API).
299 This prevents users of the public API to use this constructor (even though
300 it is public).
301*/
302QOpcUaClient::QOpcUaClient(QOpcUaClientImpl *impl, QObject *parent)
303 : QObject(*(new QOpcUaClientPrivate(impl)), parent)
304{
305 impl->m_client = this;
306
307 // callback from client implementation
308 QObject::connect(sender: impl, signal: &QOpcUaClientImpl::stateAndOrErrorChanged, context: this,
309 slot: [this](QOpcUaClient::ClientState state, QOpcUaClient::ClientError error) {
310 Q_D(QOpcUaClient);
311 d->setStateAndError(state, error);
312 if (state == QOpcUaClient::ClientState::Connected) {
313 d->updateNamespaceArray();
314 d->setupNamespaceArrayMonitoring();
315 }
316 });
317
318 QObject::connect(sender: impl, signal: &QOpcUaClientImpl::endpointsRequestFinished,
319 context: this, slot: &QOpcUaClient::endpointsRequestFinished);
320
321 QObject::connect(sender: impl, signal: &QOpcUaClientImpl::findServersFinished,
322 context: this, slot: &QOpcUaClient::findServersFinished);
323
324 QObject::connect(sender: impl, signal: &QOpcUaClientImpl::readNodeAttributesFinished,
325 context: this, slot: &QOpcUaClient::readNodeAttributesFinished);
326
327 QObject::connect(sender: impl, signal: &QOpcUaClientImpl::writeNodeAttributesFinished,
328 context: this, slot: &QOpcUaClient::writeNodeAttributesFinished);
329
330 QObject::connect(sender: impl, signal: &QOpcUaClientImpl::addNodeFinished,
331 context: this, slot: &QOpcUaClient::addNodeFinished);
332
333 QObject::connect(sender: impl, signal: &QOpcUaClientImpl::deleteNodeFinished,
334 context: this, slot: &QOpcUaClient::deleteNodeFinished);
335
336 QObject::connect(sender: impl, signal: &QOpcUaClientImpl::addReferenceFinished,
337 context: this, slot: &QOpcUaClient::addReferenceFinished);
338
339 QObject::connect(sender: impl, signal: &QOpcUaClientImpl::deleteReferenceFinished,
340 context: this, slot: &QOpcUaClient::deleteReferenceFinished);
341
342 QObject::connect(sender: impl, signal: &QOpcUaClientImpl::connectError,
343 context: this, slot: &QOpcUaClient::connectError);
344
345 QObject::connect(sender: impl, signal: &QOpcUaClientImpl::passwordForPrivateKeyRequired,
346 context: this, slot: &QOpcUaClient::passwordForPrivateKeyRequired);
347
348 QObject::connect(sender: impl, signal: &QOpcUaClientImpl::registerNodesFinished,
349 context: this, slot: &QOpcUaClient::registerNodesFinished);
350
351 QObject::connect(sender: impl, signal: &QOpcUaClientImpl::unregisterNodesFinished,
352 context: this, slot: &QOpcUaClient::unregisterNodesFinished);
353}
354
355/*!
356 Destroys the \l QOpcUaClient instance.
357*/
358QOpcUaClient::~QOpcUaClient()
359{
360}
361
362/*!
363 Sets the application identity for this \l QOpcUaClient instance to \a identity.
364 \since QtOpcUa 5.13
365*/
366void QOpcUaClient::setApplicationIdentity(const QOpcUaApplicationIdentity &identity)
367{
368 Q_D(QOpcUaClient);
369 d->setApplicationIdentity(identity);
370}
371
372/*!
373 Returns the application identity of this \l QOpcUaClient instance.
374 \since QtOpcUa 5.13
375*/
376QOpcUaApplicationIdentity QOpcUaClient::applicationIdentity() const
377{
378 Q_D(const QOpcUaClient);
379 return d->applicationIdentity();
380}
381
382/*!
383 Sets the application PKI configuration for this \l QOpcUaClient instance to \a config.
384 \since QtOpcUa 5.13
385*/
386void QOpcUaClient::setPkiConfiguration(const QOpcUaPkiConfiguration &config)
387{
388 Q_D(QOpcUaClient);
389 d->setPkiConfiguration(config);
390}
391
392/*!
393 Returns the application's PKI configuration of this \l QOpcUaClient instance.
394 \since QtOpcUa 5.13
395*/
396QOpcUaPkiConfiguration QOpcUaClient::pkiConfiguration() const
397{
398 Q_D(const QOpcUaClient);
399 return d->pkiConfiguration();
400}
401
402/*!
403 Connects to the OPC UA endpoint given in \a endpoint.
404 \since QtOpcUa 5.13
405
406 \code
407 QEndpointDescription endpointDescription;
408 ...
409 client->connectToEndpoint(endpointDescription);
410 \endcode
411
412 A list of available endpoints is usually obtained by calling \l QOpcUaClient::requestEndpoints().
413
414 If the endpoint requires username authentication, at least a user name must be set in \l QOpcUaAuthenticationInformation.
415 Calling this function before setting an authentication information will use the anonymous authentication.
416
417 \code
418 QOpcUaAuthenticationInformation authInfo;
419 authInfo.setUsernameAuthentication("user", "password");
420
421 client->setAuthenticationInformation(authInfo);
422 \endcode
423
424 \sa connected(), stateChanged(), setAuthenticationInformation(), QOpcUaEndpointDescription
425*/
426void QOpcUaClient::connectToEndpoint(const QOpcUaEndpointDescription &endpoint)
427{
428 Q_D(QOpcUaClient);
429 d->connectToEndpoint(endpoint);
430}
431
432/*!
433 Disconnects from the server.
434 \sa disconnected(), connectToEndpoint()
435*/
436void QOpcUaClient::disconnectFromEndpoint()
437{
438 Q_D(QOpcUaClient);
439 d->disconnectFromEndpoint();
440}
441
442/*!
443 Returns the description of the endpoint the client is currently connected to
444 or was last connected to.
445*/
446QOpcUaEndpointDescription QOpcUaClient::endpoint() const
447{
448 Q_D(const QOpcUaClient);
449 return d->m_endpoint;
450}
451
452QOpcUaClient::ClientState QOpcUaClient::state() const
453{
454 Q_D(const QOpcUaClient);
455 return d->m_state;
456}
457
458/*!
459 Returns the current error state of the client.
460*/
461QOpcUaClient::ClientError QOpcUaClient::error() const
462{
463 Q_D(const QOpcUaClient);
464 return d->m_error;
465}
466
467/*!
468 Returns a \l QOpcUaNode object associated with the OPC UA node identified
469 by \a nodeId. The caller becomes owner of the node object.
470
471 If the client is not connected, \c nullptr is returned. The backends may also
472 return \c nullptr for other error cases (for example for a malformed node id).
473*/
474QOpcUaNode *QOpcUaClient::node(const QString &nodeId)
475{
476 if (state() != QOpcUaClient::Connected)
477 return nullptr;
478
479 Q_D(QOpcUaClient);
480 return d->m_impl->node(nodeId);
481}
482
483/*!
484 Returns a \l QOpcUaNode object associated with the OPC UA node identified
485 by \a expandedNodeId. The caller becomes owner of the node object.
486
487 If the node is not on the currently connected server, the namespace can't be resolved,
488 the node id is malformed or the client is not connected, \c nullptr is returned.
489
490 \sa updateNamespaceArray()
491*/
492QOpcUaNode *QOpcUaClient::node(const QOpcUaExpandedNodeId &expandedNodeId)
493{
494 if (expandedNodeId.serverIndex()) {
495 qCWarning(QT_OPCUA) << "Can't create a QOpcuaNode for a node on a different server.";
496 return nullptr;
497 }
498
499 const QString nodeId = resolveExpandedNodeId(expandedNodeId);
500
501 if (!nodeId.isEmpty())
502 return node(nodeId);
503 else
504 return nullptr;
505}
506
507/*!
508 Requests an update of the namespace array from the server.
509 Returns \c true if the operation has been successfully dispatched.
510
511 The \l namespaceArrayUpdated() signal is emitted after the operation is finished.
512
513 \sa namespaceArray() namespaceArrayUpdated()
514*/
515bool QOpcUaClient::updateNamespaceArray()
516{
517 if (state() != QOpcUaClient::Connected)
518 return false;
519
520 Q_D(QOpcUaClient);
521 return d->updateNamespaceArray();
522}
523
524/*!
525 Returns the cached value of the namespace array.
526
527 The value is only valid after the \l namespaceArrayUpdated() signal has been emitted.
528
529 \sa updateNamespaceArray() namespaceArrayUpdated()
530*/
531QStringList QOpcUaClient::namespaceArray() const
532{
533 Q_D(const QOpcUaClient);
534 return d->namespaceArray();
535}
536
537/*!
538 Attempts to resolve \a expandedNodeId to a node id string with numeric namespace index.
539 Returns the node id string if the conversion was successful.
540
541 An empty string is returned if the namespace index can't be resolved or if the identifier part
542 of the expanded node id is malformed. \a ok will be set to \c true if the conversion has been successful.
543 If the expanded node id could not be resolved, \a ok will be set to \c false.
544*/
545QString QOpcUaClient::resolveExpandedNodeId(const QOpcUaExpandedNodeId &expandedNodeId, bool *ok) const
546{
547 if (expandedNodeId.serverIndex() && !expandedNodeId.namespaceUri().isEmpty()) {
548 qCWarning(QT_OPCUA) << "Can't resolve a namespace index on a different server.";
549 if (ok)
550 *ok = false;
551 return QString();
552 }
553
554 if (expandedNodeId.namespaceUri().isEmpty()) {
555 if (ok)
556 *ok = true;
557 return expandedNodeId.nodeId();
558 } else {
559 if (!namespaceArray().size()) {
560 qCWarning(QT_OPCUA) << "Namespaces table missing, unable to resolve namespace URI.";
561 if (ok)
562 *ok = false;
563 return QString();
564 }
565
566 int index = namespaceArray().indexOf(str: expandedNodeId.namespaceUri());
567
568 if (index < 0) {
569 qCWarning(QT_OPCUA) << "Failed to resolve namespace" << expandedNodeId.namespaceUri();
570 if (ok)
571 *ok = false;
572 return QString();
573 }
574
575 QStringList splitId = expandedNodeId.nodeId().split(sep: ';'_L1);
576 if (splitId.size() != 2) {
577 qCWarning(QT_OPCUA) << "Failed to split node id" << expandedNodeId.nodeId();
578 if (ok)
579 *ok = false;
580 return QString();
581 }
582
583 if (ok)
584 *ok = true;
585 return u"ns=%1;"_s.arg(a: index).append(s: splitId.at(i: 1));
586 }
587}
588
589/*!
590 Attempts to create a qualified name from \a namespaceUri and the name string \a name.
591 Returns the resulting qualified name. An empty qualified name is returned if
592 \a namespaceUri can't be resolved.
593
594 \a ok will be set to \c true if the namespace URI resolution has been successful.
595 If the namespace URI could not be resolved, \a ok will be set to \c false.
596*/
597QOpcUaQualifiedName QOpcUaClient::qualifiedNameFromNamespaceUri(const QString &namespaceUri, const QString &name, bool *ok) const
598{
599 if (namespaceArray().isEmpty()) {
600 qCWarning(QT_OPCUA) << "Namespaces table missing, unable to resolve namespace URI.";
601 if (ok)
602 *ok = false;
603 return QOpcUaQualifiedName();
604 }
605
606 int index = namespaceArray().indexOf(str: namespaceUri);
607
608 if (index < 0) {
609 qCWarning(QT_OPCUA) << "Failed to resolve namespace" << namespaceUri;
610 if (ok)
611 *ok = false;
612 return QOpcUaQualifiedName();
613 }
614
615 if (ok)
616 *ok = true;
617
618 return QOpcUaQualifiedName(index, name);
619};
620
621/*!
622 Adds the node described by \a nodeToAdd on the server.
623
624 Returns \c true if the asynchronous call has been successfully dispatched.
625
626 The success of the operation is returned in the \l addNodeFinished() signal.
627
628 The following example code adds new a Variable node on the server:
629
630 \code
631 QOpcUaNodeCreationAttributes attributes;
632 attributes.setDisplayName(QOpcUaLocalizedText("en", "My new Variable node"));
633 attributes.setDescription(QOpcUaLocalizedText("en", "A node which has been added at runtime"));
634 attributes.setValue(23.0, QOpcUa::Types::Double);
635 attributes.setDataTypeId(QOpcUa::ns0ID(QOpcUa::NodeIds::Namespace0::Double));
636 attributes.setValueRank(-2); // Scalar or array
637 attributes.setAccessLevel(QOpcUa::AccessLevelBit::CurrentRead);
638 attributes.setUserAccessLevel(QOpcUa::AccessLevelBit::CurrentRead);
639
640 QOpcUaAddNodeItem item;
641 item.setParentNodeId(QOpcUaExpandedNodeId("ns=3;s=TestFolder"));
642 item.setReferenceTypeId(QOpcUa::nodeIdFromReferenceType(QOpcUa::ReferenceTypeId::Organizes));
643 item.setRequestedNewNodeId(QOpcUaExpandedNodeId("ns=3;s=MyNewVariableNode"));
644 item.setBrowseName(QOpcUaQualifiedName(3, "MyNewVariableNode"));
645 item.setNodeClass(QOpcUa::NodeClass::Variable);
646 item.setNodeAttributes(attributes);
647
648 m_client->addNode(item);
649 \endcode
650
651 \sa deleteNode() addNodeFinished() QOpcUaAddNodeItem
652*/
653bool QOpcUaClient::addNode(const QOpcUaAddNodeItem &nodeToAdd)
654{
655 if (state() != QOpcUaClient::Connected)
656 return false;
657
658 Q_D(QOpcUaClient);
659 return d->m_impl->addNode(nodeToAdd);
660}
661
662/*!
663 Deletes the node with node id \a nodeId from the server.
664 If \a deleteTargetReferences is \c false, only the references with source node \a nodeId are deleted.
665 If \a deleteTargetReferences is \c true, references with \a nodeId as target are deleted too.
666
667 Returns \c true if the asynchronous call has been successfully dispatched.
668
669 The success of the operation is returned in the \l deleteNodeFinished() signal.
670
671 The following example code deletes a node and all references to it from the server:
672
673 \code
674 m_client->deleteNode(QOpcUaExpandedNodeId("ns=3;s=MyNewVariableNode"), true);
675 \endcode
676
677 \sa addNode() deleteNodeFinished()
678*/
679bool QOpcUaClient::deleteNode(const QString &nodeId, bool deleteTargetReferences)
680{
681 if (state() != QOpcUaClient::Connected)
682 return false;
683
684 Q_D(QOpcUaClient);
685 return d->m_impl->deleteNode(nodeId, deleteTargetReferences);
686}
687
688/*!
689 Adds the reference described by \a referenceToAdd to the server.
690
691 Returns \c true if the asynchronous call has been successfully dispatched.
692
693 The success of the operation is returned in the \l addReferenceFinished() signal.
694
695 The following example code adds a reference to a node to the "Objects" folder:
696
697 \code
698 QOpcUaAddReferenceItem item;
699 item.setSourceNodeId(QOpcUa::namespace0Id(QOpcUa::NodeIds::Namespace0::ObjectsFolder));
700 item.setReferenceTypeId(QOpcUa::nodeIdFromInteger(0, static_cast<quint32>(QOpcUa::ReferenceTypeId::Organizes)));
701 item.setIsForwardReference(true);
702 item.setTargetNodeId(QOpcUaExpandedNodeId("ns=3;s=MyNewVariableNode"));
703 item.setTargetNodeClass(QOpcUa::NodeClass::Variable);
704
705 m_client->addReference(item);
706 \endcode
707
708 \sa deleteReference() addReferenceFinished() QOpcUaAddReferenceItem
709*/
710bool QOpcUaClient::addReference(const QOpcUaAddReferenceItem &referenceToAdd)
711{
712 if (state() != QOpcUaClient::Connected)
713 return false;
714
715 Q_D(QOpcUaClient);
716 return d->m_impl->addReference(referenceToAdd);
717}
718
719/*!
720 Deletes the reference described by \a referenceToDelete from the server.
721
722 Returns \c true if the asynchronous call has been successfully dispatched.
723
724 The success of the operation is returned in the \l deleteReferenceFinished() signal.
725
726 The following example code deletes a reference to a node from the "Objects" folder:
727
728 \code
729 QOpcUaDeleteReferenceItem item;
730 item.setSourceNodeId(QOpcUa::namespace0Id(QOpcUa::NodeIds::Namespace0::ObjectsFolder));
731 item.setReferenceTypeId(QOpcUa::nodeIdFromInteger(0, static_cast<quint32>(QOpcUa::ReferenceTypeId::Organizes)));
732 item.setIsForwardReference(true);
733 item.setTargetNodeId(QOpcUaExpandedNodeId("ns=3;s=MyNewVariableNode"));
734 item.setDeleteBidirectional(true);
735
736 m_client->deleteReference(item);
737 \endcode
738
739 \sa addReference() deleteReferenceFinished() QOpcUaDeleteReferenceItem
740*/
741bool QOpcUaClient::deleteReference(const QOpcUaDeleteReferenceItem &referenceToDelete)
742{
743 if (state() != QOpcUaClient::Connected)
744 return false;
745
746 Q_D(QOpcUaClient);
747 return d->m_impl->deleteReference(referenceToDelete);
748}
749
750/*!
751 Starts an asynchronous \c GetEndpoints request to read a list of available endpoints
752 from the server at \a url.
753 Returns \c true if the asynchronous call has been successfully dispatched.
754
755 The endpoint information is returned in the \l endpointsRequestFinished() signal.
756*/
757bool QOpcUaClient::requestEndpoints(const QUrl &url)
758{
759 Q_D(QOpcUaClient);
760 return d->m_impl->requestEndpoints(url);
761}
762
763/*!
764 Starts an asynchronous FindServers request to read a list of known servers from a server or
765 discovery server at \a url.
766 Returns \c true if the asynchronous call has been successfully dispatched.
767
768 \a localeIds can be used to select the language of the application names returned by the request.
769 The format is specified in OPC UA 1.05 part 3, 8.4, for example "en" for English, or "de-DE" for
770 German (Germany). If more than one locale ID is specified, the server uses the first match. If there
771 is no match or \a localeIds is empty, a default locale is chosen by the server.
772
773 \a serverUris may be used to restrict the results to servers with a matching applicationUri in their
774 application description. For example, finding the current URL of the server with the applicationUri
775 "MyPLC", the following call can be used:
776
777 \code
778 client->findServers(discoveryServerUrl, QStringList(), QStringList({"MyPLC"}));
779 \endcode
780
781 The results are returned in the \l findServersFinished() signal.
782*/
783bool QOpcUaClient::findServers(const QUrl &url, const QStringList &localeIds, const QStringList &serverUris)
784{
785 Q_D(QOpcUaClient);
786 return d->m_impl->findServers(url, localeIds, serverUris);
787}
788
789/*!
790 Starts a read of multiple attributes on different nodes.
791 The node id, the attribute and an index range can be specified for every entry in \a nodesToRead.
792
793 Returns true if the asynchronous request has been successfully dispatched.
794 The results are returned in the \l readNodeAttributesFinished() signal.
795
796 This read function offers an alternative way to read attributes of nodes which can be used
797 for scenarios where the values of a large number of node attributes on different nodes must be read
798 without requiring the other features of the \l QOpcUaNode based API like monitoring for value changes.
799 All read items in the request are sent to the server in a single request and are answered in a single
800 response which generates a single \l readNodeAttributesFinished() signal. This reduces the network overhead and
801 the number of signal slot connections if many different nodes are involved.
802
803 In the following example, the display name attribute and the two index ranges "0:2" and "5:7" of the value
804 attribute of the same node and the entire value attribute of a second node are read using a single service call:
805 \code
806 QList<QOpcUaReadItem> request;
807 request.push_back(QOpcUaReadItem("ns=1;s=MyArrayNode",
808 QOpcUa::NodeAttribute::DisplayName));
809 request.push_back(QOpcUaReadItem("ns=1;s=MyArrayNode",
810 QOpcUa::NodeAttribute::Value, "0:2"));
811 request.push_back(QOpcUaReadItem("ns=1;s=MyArrayNode",
812 QOpcUa::NodeAttribute::Value, "5:7"));
813 request.push_back(QOpcUaReadItem("ns=1;s=MyScalarNode));
814 m_client->readNodeAttributes(request);
815 \endcode
816
817 \sa QOpcUaReadItem readNodeAttributesFinished()
818*/
819bool QOpcUaClient::readNodeAttributes(const QList<QOpcUaReadItem> &nodesToRead)
820{
821 if (state() != QOpcUaClient::Connected)
822 return false;
823
824 Q_D(QOpcUaClient);
825 return d->m_impl->readNodeAttributes(nodesToRead);
826}
827
828/*!
829 Starts a write for multiple attributes on different nodes.
830 The node id, the attribute, the value, the value type and an index range can be specified
831 for every entry in \a nodesToWrite.
832
833 Returns \c true if the asynchronous request has been successfully dispatched.
834 The results are returned in the \l writeNodeAttributesFinished() signal.
835
836 This write function offers an alternative way to write attributes of nodes which can be used
837 for scenarios where the values of a large number of node attributes on different nodes must be written
838 without requiring the other features of the \l QOpcUaNode based API like monitoring for value changes.
839 All write items in the request are sent to the server in a single request and are answered in a single
840 response which generates a single \l writeNodeAttributesFinished() signal. This reduces the network overhead and
841 the number of signal slot connections if many different nodes are involved.
842
843 In the following example, the Values attributes of two different nodes are written in one call.
844 The second node has an array value of which only the first two elements are overwritten:
845
846 \code
847 QList<QOpcUaWriteItem> request;
848
849 request.append(QOpcUaWriteItem("ns=2;s=Demo.Static.Scalar.Double", QOpcUa::NodeAttribute::Value,
850 23.0, QOpcUa::Types::Double));
851 request.append(QOpcUaWriteItem("ns=2;s=Demo.Static.Arrays.UInt32", QOpcUa::NodeAttribute::Value,
852 QVariantList({0, 1, 2}), QOpcUa::Types::UInt32, "0:2"));
853
854 m_client->writeNodeAttributes(request);
855 \endcode
856
857 \sa QOpcUaWriteItem writeNodeAttributesFinished()
858*/
859bool QOpcUaClient::writeNodeAttributes(const QList<QOpcUaWriteItem> &nodesToWrite)
860{
861 if (state() != QOpcUaClient::Connected)
862 return false;
863
864 Q_D(QOpcUaClient);
865 return d->m_impl->writeNodeAttributes(nodesToWrite);
866}
867
868/*!
869 Returns the name of the backend used by this instance of QOpcUaClient,
870 e.g. "open62541".
871*/
872QString QOpcUaClient::backend() const
873{
874 Q_D(const QOpcUaClient);
875 return d->m_impl->backend();
876}
877
878/*!
879 Enables automatic update of the namespace table.
880
881 Enabling this will keep the local copy of the namespace table updated automatically.
882 \l namespaceArrayUpdated will be emitted when the array changed.
883 \a isEnabled determines if autoupdate is being enabled or disabled.
884
885 A subscription will be made on the node on the server to keep track of changes.
886 In case a server does not support subscriptions this will not work and
887 \l isNamespaceAutoupdateEnabled returns \c false.
888
889 \sa namespaceArray() namespaceArrayUpdated()
890*/
891void QOpcUaClient::setNamespaceAutoupdate(bool isEnabled)
892{
893 Q_D(QOpcUaClient);
894 d->m_enableNamespaceArrayAutoupdate = isEnabled;
895 d->setupNamespaceArrayMonitoring();
896}
897
898/*!
899 Returns whether autoupdate of the namespace array is enabled.
900*/
901bool QOpcUaClient::isNamespaceAutoupdateEnabled() const
902{
903 Q_D(const QOpcUaClient);
904 return d->m_enableNamespaceArrayAutoupdate;
905}
906
907/*!
908 Sets the interval for the namespace table subscription.
909
910 The subscription may be revised by the server.
911
912 \a interval determines the interval to check for changes in milliseconds. The default is once per second.
913
914 \sa QOpcUaClient::setNamespaceAutoupdate(bool isEnabled)
915*/
916void QOpcUaClient::setNamespaceAutoupdateInterval(int interval)
917{
918 Q_D(QOpcUaClient);
919 d->m_namespaceArrayUpdateInterval = interval;
920 d->setupNamespaceArrayMonitoring();
921}
922
923/*!
924 Returns the current revised update interval of the namespace array.
925
926 \sa setNamespaceAutoupdateInterval(int interval)
927*/
928int QOpcUaClient::namespaceAutoupdateInterval() const
929{
930 Q_D(const QOpcUaClient);
931 return d->m_namespaceArrayUpdateInterval;
932}
933
934/*!
935 Sets the authentication information of this client to \a authenticationInformation.
936
937 \sa connectToEndpoint()
938*/
939void QOpcUaClient::setAuthenticationInformation(const QOpcUaAuthenticationInformation &authenticationInformation)
940{
941 Q_D(QOpcUaClient);
942 d->m_authenticationInformation = authenticationInformation;
943}
944
945/*!
946 Returns the current authentication information.
947*/
948const QOpcUaAuthenticationInformation &QOpcUaClient::authenticationInformation() const
949{
950 Q_D(const QOpcUaClient);
951 return d->m_authenticationInformation;
952}
953
954/*!
955 \since 6.6
956
957 Sets the connection settings for this client to \a connectionSettings.
958
959 Example:
960 \code
961 QOpcUaConnectionSettings settings;
962 // Ask the server to give localized texts in german with french as fallback
963 settings.setSessionLocaleIds({ "de", "fr" });
964 // We need to call some long running methods, increase the request timeout
965 settings.setRequestTimeout(std::chrono::minutes(2));
966 opcuaClient->setConnectionSettings(settings);
967 \endcode
968
969 The values from \a connectionSettings are applied to any new connections after this point.
970
971 The \c open62541 plugin supports updating the session locale ids for the current connection.
972 All other values that are modified but do not support being updated while connected cause
973 a warning message and will be applied during the next call to \l connectToEndpoint().
974
975 \sa connectionSettings()
976 */
977void QOpcUaClient::setConnectionSettings(const QOpcUaConnectionSettings &connectionSettings)
978{
979 Q_D(QOpcUaClient);
980
981 if (connectionSettings != d->m_connectionSettings) {
982 d->m_connectionSettings = connectionSettings;
983 emit d->m_impl->connectionSettingsChanged(settings: connectionSettings);
984 }
985}
986
987/*!
988 \since 6.6
989
990 Returns the connection settings for this client.
991
992 \sa setConnectionSettings()
993 */
994QOpcUaConnectionSettings QOpcUaClient::connectionSettings() const
995{
996 Q_D(const QOpcUaClient);
997 return d->m_connectionSettings;
998}
999
1000/*!
1001 \since QtOpcUa 5.14
1002
1003 Returns the security policies supported by the used backend.
1004
1005 This function is currently available as a Technology Preview, and therefore the API
1006 and functionality provided by the function may be subject to change at any time without
1007 prior notice.
1008*/
1009QStringList QOpcUaClient::supportedSecurityPolicies() const
1010{
1011 Q_D(const QOpcUaClient);
1012 return d->m_impl->supportedSecurityPolicies();
1013}
1014
1015/*!
1016 \since QtOpcUa 5.14
1017
1018 Returns the user token types supported by the used backend.
1019
1020 This function is currently available as a Technology Preview, and therefore the API
1021 and functionality provided by the function may be subject to change at any time without
1022 prior notice.
1023
1024 \sa QOpcUaUserTokenPolicy::TokenType
1025*/
1026QList<QOpcUaUserTokenPolicy::TokenType> QOpcUaClient::supportedUserTokenTypes() const
1027{
1028 Q_D(const QOpcUaClient);
1029 return d->m_impl->supportedUserTokenTypes();
1030}
1031
1032/*!
1033 \since 6.3
1034
1035 Starts a read raw history \a request for one or multiple nodes. This is the Qt OPC UA representation for the OPC UA
1036 ReadHistory service for reading raw historical data defined in
1037 \l {https://reference.opcfoundation.org/v105/Core/docs/Part4/5.10.3/} {OPC UA 1.05 part 4, 5.10.3}.
1038
1039 The start timestamp, end timestamp, number of values per node, returnBounds and nodes to read
1040 can be specified in a \l QOpcUaHistoryReadRawRequest.
1041
1042 Returns a \l QOpcUaHistoryReadResponse which contains the state of the request if the asynchronous
1043 request has been successfully dispatched. The results are returned in the
1044 \l QOpcUaHistoryReadResponse::readHistoryDataFinished(const QList<QOpcUaHistoryData> &results, QOpcUa::UaStatusCode serviceResult)
1045 signal.
1046
1047 In the following example, the historic data from the last two days of two nodes are requested and printed.
1048 The result is limited to ten values per node.
1049
1050 \code
1051 QOpcUaHistoryReadRawRequest request(
1052 { QOpcUaReadItem("ns=1;s=myValue1"), QOpcUaReadItem("ns=1;s=myValue2") },
1053 QDateTime::currentDateTime(),
1054 QDateTime::currentDateTime().addDays(-2),
1055 10,
1056 true);
1057
1058 QOpcUaHistoryReadResponse *response = m_client->readHistoryData(request);
1059 if (response) {
1060 QObject::connect(response, &QOpcUaHistoryReadResponse::readHistoryDataFinished,
1061 [] (QList<QOpcUaHistoryData> results, QOpcUa::UaStatusCode serviceResult) {
1062 if (serviceResult != QOpcUa::UaStatusCode::Good) {
1063 qWarning() << "Fetching historical data failed with:" << serviceResult;
1064 } else {
1065 for (const auto& result : results) {
1066 qInfo() << "NodeId:" << result.nodeId();
1067 for (const auto &dataValue : result.result())
1068 qInfo() << "Value:" << dataValue.value();
1069 }
1070 }
1071 });
1072 }
1073 \endcode
1074*/
1075QOpcUaHistoryReadResponse *QOpcUaClient::readHistoryData(const QOpcUaHistoryReadRawRequest &request)
1076{
1077 Q_D(const QOpcUaClient);
1078 return d->m_impl->readHistoryData(request);
1079}
1080
1081/*!
1082 \since 6.7
1083
1084 Registers the node ids in \a nodesToRegister on the server and returns \c true if the request
1085 has been successfully dispatched.
1086 The results are returned in the \l registerNodesFinished() signal.
1087
1088 The node registration service is used to let the server know that a node will be accessed frequently
1089 so it may perform operations like keeping the connection to an external resource open.
1090 The server may also return an alias node id which is recommended to be numeric. This might come in
1091 handy if a node with a long string identifier node id is used in many requests.
1092 The real performance gain (if any) depends on the server's implementation.
1093
1094 The registered node ids are only guaranteed to be valid for the current session.
1095 Any registrations that are no longer needed should be unregistered as soon as possible so the
1096 server may free the associated resources.
1097
1098 \sa unregisterNodes()
1099 */
1100bool QOpcUaClient::registerNodes(const QStringList &nodesToRegister)
1101{
1102 Q_D(const QOpcUaClient);
1103 return d->m_impl->registerNodes(nodesToRegister);
1104}
1105
1106/*!
1107 \since 6.7
1108
1109 Unregisters the node ids in \a nodesToUnregister on the server and returns \c true if the request
1110 has been successfully dispatched.
1111 The results are returned in the \l unregisterNodesFinished() signal.
1112
1113 The node ids to pass in \a nodesToUnregister must have been obtained via \l registerNodes().
1114
1115 \sa registerNodes()
1116 */
1117bool QOpcUaClient::unregisterNodes(const QStringList &nodesToUnregister)
1118{
1119 Q_D(const QOpcUaClient);
1120 return d->m_impl->unregisterNodes(nodesToUnregister);
1121}
1122
1123/*!
1124 \since 6.7
1125
1126 Starts a read event history request for one or multiple node ids with the parameters in \a request.
1127
1128 Returns a \l QOpcUaHistoryReadResponse which contains the state of the request if the asynchronous
1129 request has been successfully dispatched. The results are returned in the
1130 \l QOpcUaHistoryReadResponse::readHistoryEventsFinished(const QList<QOpcUaHistoryEvent> &results, QOpcUa::UaStatusCode serviceResult)
1131 signal.
1132
1133 The following example retrieves historic events for the last two days for two nodes. Up to 10 events per node are returned at a time.
1134 While there are more events matching the filter and the provided time range, \c hasMoreData() will be true and more events can be
1135 fetched via \b readMoreData().
1136
1137 \code
1138 QOpcUaMonitoringParameters::EventFilter filter;
1139 filter << QOpcUaSimpleAttributeOperand("Message");
1140 filter << QOpcUaSimpleAttributeOperand("Time");
1141
1142 const QOpcUaHistoryReadEventRequest request({ QOpcUaReadItem("ns=2;s=EventHistorian"), QOpcUaReadItem("ns=2;s=EventHistorian2") },
1143 QDateTime::currentDateTime().addDays(-2), QDateTime::currentDateTime(),
1144 filter, 10);
1145
1146 // The response object must be freed by the user after all wanted data has been retrieved
1147 const auto response = opcuaClient->readHistoryEvents(request);
1148
1149 QObject::connect(response, &QOpcUaHistoryReadResponse::readHistoryEventsFinished, this,
1150 [response](const QList<QOpcUaHistoryEvent> &results, QOpcUa::UaStatusCode serviceResult) {
1151 if (serviceResult != QOpcUa::UaStatusCode::Good) {
1152 qDebug() << "Service call failed with" << serviceResult;
1153 return;
1154 }
1155
1156 // Print what we got so far
1157 for (const auto &result : response->events()) {
1158 qDebug() << "Results for" << result.nodeId() << result.statusCode();
1159 for (const auto &event : result.events())
1160 qDebug() << " Event:" << event;
1161 }
1162
1163 if (response->hasMoreData())
1164 response->readMoreData();
1165 });
1166 \endcode
1167*/
1168QOpcUaHistoryReadResponse *QOpcUaClient::readHistoryEvents(const QOpcUaHistoryReadEventRequest &request)
1169{
1170 Q_D(const QOpcUaClient);
1171 return d->m_impl->readHistoryEvents(request);
1172}
1173
1174QT_END_NAMESPACE
1175

source code of qtopcua/src/opcua/client/qopcuaclient.cpp