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

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

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