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 | |
14 | QT_BEGIN_NAMESPACE |
15 | |
16 | Q_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 | \internal QOpcUaClientImpl is an opaque type (as seen from the public API). |
266 | This prevents users of the public API to use this constructor (even though |
267 | it is public). |
268 | */ |
269 | QOpcUaClient::QOpcUaClient(QOpcUaClientImpl *impl, QObject *parent) |
270 | : QObject(*(new QOpcUaClientPrivate(impl)), parent) |
271 | { |
272 | impl->m_client = this; |
273 | } |
274 | |
275 | /*! |
276 | Destroys the \l QOpcUaClient instance. |
277 | */ |
278 | QOpcUaClient::~QOpcUaClient() |
279 | { |
280 | } |
281 | |
282 | /*! |
283 | Sets the application identity for this \l QOpcUaClient instance to \a identity. |
284 | \since QtOpcUa 5.13 |
285 | */ |
286 | void QOpcUaClient::setApplicationIdentity(const QOpcUaApplicationIdentity &identity) |
287 | { |
288 | Q_D(QOpcUaClient); |
289 | d->setApplicationIdentity(identity); |
290 | } |
291 | |
292 | /*! |
293 | Returns the application identity of this \l QOpcUaClient instance. |
294 | \since QtOpcUa 5.13 |
295 | */ |
296 | QOpcUaApplicationIdentity QOpcUaClient::applicationIdentity() const |
297 | { |
298 | Q_D(const QOpcUaClient); |
299 | return d->applicationIdentity(); |
300 | } |
301 | |
302 | /*! |
303 | Sets the application PKI configuration for this \l QOpcUaClient instance to \a config. |
304 | \since QtOpcUa 5.13 |
305 | */ |
306 | void QOpcUaClient::setPkiConfiguration(const QOpcUaPkiConfiguration &config) |
307 | { |
308 | Q_D(QOpcUaClient); |
309 | d->setPkiConfiguration(config); |
310 | } |
311 | |
312 | /*! |
313 | Returns the application's PKI configuration of this \l QOpcUaClient instance. |
314 | \since QtOpcUa 5.13 |
315 | */ |
316 | QOpcUaPkiConfiguration QOpcUaClient::pkiConfiguration() const |
317 | { |
318 | Q_D(const QOpcUaClient); |
319 | return d->pkiConfiguration(); |
320 | } |
321 | |
322 | /*! |
323 | Connects to the OPC UA endpoint given in \a endpoint. |
324 | \since QtOpcUa 5.13 |
325 | |
326 | \code |
327 | QEndpointDescription endpointDescription; |
328 | ... |
329 | client->connectToEndpoint(endpointDescription); |
330 | \endcode |
331 | |
332 | A list of available endpoints is usually obtained by calling \l QOpcUaClient::requestEndpoints(). |
333 | |
334 | If the endpoint requires username authentication, at least a user name must be set in \l QOpcUaAuthenticationInformation. |
335 | Calling this function before setting an authentication information will use the anonymous authentication. |
336 | |
337 | \code |
338 | QOpcUaAuthenticationInformation authInfo; |
339 | authInfo.setUsernameAuthentication("user", "password"); |
340 | |
341 | client->setAuthenticationInformation(authInfo); |
342 | \endcode |
343 | |
344 | \sa connected(), stateChanged(), setAuthenticationInformation(), QOpcUaEndpointDescription |
345 | */ |
346 | void QOpcUaClient::connectToEndpoint(const QOpcUaEndpointDescription &endpoint) |
347 | { |
348 | Q_D(QOpcUaClient); |
349 | d->connectToEndpoint(endpoint); |
350 | } |
351 | |
352 | /*! |
353 | Disconnects from the server. |
354 | \sa disconnected(), connectToEndpoint() |
355 | */ |
356 | void QOpcUaClient::disconnectFromEndpoint() |
357 | { |
358 | Q_D(QOpcUaClient); |
359 | d->disconnectFromEndpoint(); |
360 | } |
361 | |
362 | /*! |
363 | Returns the description of the endpoint the client is currently connected to |
364 | or was last connected to. |
365 | */ |
366 | QOpcUaEndpointDescription QOpcUaClient::endpoint() const |
367 | { |
368 | Q_D(const QOpcUaClient); |
369 | return d->m_endpoint; |
370 | } |
371 | |
372 | QOpcUaClient::ClientState QOpcUaClient::state() const |
373 | { |
374 | Q_D(const QOpcUaClient); |
375 | return d->m_state; |
376 | } |
377 | |
378 | /*! |
379 | Returns the current error state of the client. |
380 | */ |
381 | QOpcUaClient::ClientError QOpcUaClient::error() const |
382 | { |
383 | Q_D(const QOpcUaClient); |
384 | return d->m_error; |
385 | } |
386 | |
387 | /*! |
388 | Returns a \l QOpcUaNode object associated with the OPC UA node identified |
389 | by \a nodeId. The caller becomes owner of the node object. |
390 | |
391 | If the client is not connected, \c nullptr is returned. The backends may also |
392 | return \c nullptr for other error cases (for example for a malformed node id). |
393 | */ |
394 | QOpcUaNode *QOpcUaClient::node(const QString &nodeId) |
395 | { |
396 | if (state() != QOpcUaClient::Connected) |
397 | return nullptr; |
398 | |
399 | Q_D(QOpcUaClient); |
400 | return d->m_impl->node(nodeId); |
401 | } |
402 | |
403 | /*! |
404 | Returns a \l QOpcUaNode object associated with the OPC UA node identified |
405 | by \a expandedNodeId. The caller becomes owner of the node object. |
406 | |
407 | If the node is not on the currently connected server, the namespace can't be resolved, |
408 | the node id is malformed or the client is not connected, \c nullptr is returned. |
409 | |
410 | \sa updateNamespaceArray() |
411 | */ |
412 | QOpcUaNode *QOpcUaClient::node(const QOpcUaExpandedNodeId &expandedNodeId) |
413 | { |
414 | if (expandedNodeId.serverIndex()) { |
415 | qCWarning(QT_OPCUA) << "Can't create a QOpcuaNode for a node on a different server." ; |
416 | return nullptr; |
417 | } |
418 | |
419 | const QString nodeId = resolveExpandedNodeId(expandedNodeId); |
420 | |
421 | if (!nodeId.isEmpty()) |
422 | return node(nodeId); |
423 | else |
424 | return nullptr; |
425 | } |
426 | |
427 | /*! |
428 | Requests an update of the namespace array from the server. |
429 | Returns \c true if the operation has been successfully dispatched. |
430 | |
431 | The \l namespaceArrayUpdated() signal is emitted after the operation is finished. |
432 | |
433 | \sa namespaceArray() namespaceArrayUpdated() |
434 | */ |
435 | bool QOpcUaClient::updateNamespaceArray() |
436 | { |
437 | if (state() != QOpcUaClient::Connected) |
438 | return false; |
439 | |
440 | Q_D(QOpcUaClient); |
441 | return d->updateNamespaceArray(); |
442 | } |
443 | |
444 | /*! |
445 | Returns the cached value of the namespace array. |
446 | |
447 | The value is only valid after the \l namespaceArrayUpdated() signal has been emitted. |
448 | |
449 | \sa updateNamespaceArray() namespaceArrayUpdated() |
450 | */ |
451 | QStringList QOpcUaClient::namespaceArray() const |
452 | { |
453 | Q_D(const QOpcUaClient); |
454 | return d->namespaceArray(); |
455 | } |
456 | |
457 | /*! |
458 | Attempts to resolve \a expandedNodeId to a node id string with numeric namespace index. |
459 | Returns the node id string if the conversion was successful. |
460 | |
461 | An empty string is returned if the namespace index can't be resolved or if the identifier part |
462 | of the expanded node id is malformed. \a ok will be set to \c true if the conversion has been successful. |
463 | If the expanded node id could not be resolved, \a ok will be set to \c false. |
464 | */ |
465 | QString QOpcUaClient::resolveExpandedNodeId(const QOpcUaExpandedNodeId &expandedNodeId, bool *ok) const |
466 | { |
467 | if (expandedNodeId.serverIndex() && !expandedNodeId.namespaceUri().isEmpty()) { |
468 | qCWarning(QT_OPCUA) << "Can't resolve a namespace index on a different server." ; |
469 | if (ok) |
470 | *ok = false; |
471 | return QString(); |
472 | } |
473 | |
474 | if (expandedNodeId.namespaceUri().isEmpty()) { |
475 | if (ok) |
476 | *ok = true; |
477 | return expandedNodeId.nodeId(); |
478 | } else { |
479 | if (!namespaceArray().size()) { |
480 | qCWarning(QT_OPCUA) << "Namespaces table missing, unable to resolve namespace URI." ; |
481 | if (ok) |
482 | *ok = false; |
483 | return QString(); |
484 | } |
485 | |
486 | int index = namespaceArray().indexOf(str: expandedNodeId.namespaceUri()); |
487 | |
488 | if (index < 0) { |
489 | qCWarning(QT_OPCUA) << "Failed to resolve namespace" << expandedNodeId.namespaceUri(); |
490 | if (ok) |
491 | *ok = false; |
492 | return QString(); |
493 | } |
494 | |
495 | QStringList splitId = expandedNodeId.nodeId().split(sep: QLatin1String(";" )); |
496 | if (splitId.size() != 2) { |
497 | qCWarning(QT_OPCUA) << "Failed to split node id" << expandedNodeId.nodeId(); |
498 | if (ok) |
499 | *ok = false; |
500 | return QString(); |
501 | } |
502 | |
503 | if (ok) |
504 | *ok = true; |
505 | return QStringLiteral("ns=%1;" ).arg(a: index).append(s: splitId.at(i: 1)); |
506 | } |
507 | } |
508 | |
509 | /*! |
510 | Attempts to create a qualified name from \a namespaceUri and the name string \a name. |
511 | Returns the resulting qualified name. An empty qualified name is returned if |
512 | \a namespaceUri can't be resolved. |
513 | |
514 | \a ok will be set to \c true if the namespace URI resolution has been successful. |
515 | If the namespace URI could not be resolved, \a ok will be set to \c false. |
516 | */ |
517 | QOpcUaQualifiedName QOpcUaClient::qualifiedNameFromNamespaceUri(const QString &namespaceUri, const QString &name, bool *ok) const |
518 | { |
519 | if (namespaceArray().isEmpty()) { |
520 | qCWarning(QT_OPCUA) << "Namespaces table missing, unable to resolve namespace URI." ; |
521 | if (ok) |
522 | *ok = false; |
523 | return QOpcUaQualifiedName(); |
524 | } |
525 | |
526 | int index = namespaceArray().indexOf(str: namespaceUri); |
527 | |
528 | if (index < 0) { |
529 | qCWarning(QT_OPCUA) << "Failed to resolve namespace" << namespaceUri; |
530 | if (ok) |
531 | *ok = false; |
532 | return QOpcUaQualifiedName(); |
533 | } |
534 | |
535 | if (ok) |
536 | *ok = true; |
537 | |
538 | return QOpcUaQualifiedName(index, name); |
539 | }; |
540 | |
541 | /*! |
542 | Adds the node described by \a nodeToAdd on the server. |
543 | |
544 | Returns \c true if the asynchronous call has been successfully dispatched. |
545 | |
546 | The success of the operation is returned in the \l addNodeFinished() signal. |
547 | |
548 | The following example code adds new a Variable node on the server: |
549 | |
550 | \code |
551 | QOpcUaNodeCreationAttributes attributes; |
552 | attributes.setDisplayName(QOpcUaLocalizedText("en", "My new Variable node")); |
553 | attributes.setDescription(QOpcUaLocalizedText("en", "A node which has been added at runtime")); |
554 | attributes.setValue(23.0, QOpcUa::Types::Double); |
555 | attributes.setDataTypeId(QOpcUa::ns0ID(QOpcUa::NodeIds::Namespace0::Double)); |
556 | attributes.setValueRank(-2); // Scalar or array |
557 | attributes.setAccessLevel(QOpcUa::AccessLevelBit::CurrentRead); |
558 | attributes.setUserAccessLevel(QOpcUa::AccessLevelBit::CurrentRead); |
559 | |
560 | QOpcUaAddNodeItem item; |
561 | item.setParentNodeId(QOpcUaExpandedNodeId("ns=3;s=TestFolder")); |
562 | item.setReferenceTypeId(QOpcUa::nodeIdFromReferenceType(QOpcUa::ReferenceTypeId::Organizes)); |
563 | item.setRequestedNewNodeId(QOpcUaExpandedNodeId("ns=3;s=MyNewVariableNode")); |
564 | item.setBrowseName(QOpcUaQualifiedName(3, "MyNewVariableNode")); |
565 | item.setNodeClass(QOpcUa::NodeClass::Variable); |
566 | item.setNodeAttributes(attributes); |
567 | |
568 | m_client->addNode(item); |
569 | \endcode |
570 | |
571 | \sa deleteNode() addNodeFinished() QOpcUaAddNodeItem |
572 | */ |
573 | bool QOpcUaClient::addNode(const QOpcUaAddNodeItem &nodeToAdd) |
574 | { |
575 | if (state() != QOpcUaClient::Connected) |
576 | return false; |
577 | |
578 | Q_D(QOpcUaClient); |
579 | return d->m_impl->addNode(nodeToAdd); |
580 | } |
581 | |
582 | /*! |
583 | Deletes the node with node id \a nodeId from the server. |
584 | If \a deleteTargetReferences is \c false, only the references with source node \a nodeId are deleted. |
585 | If \a deleteTargetReferences is \c true, references with \a nodeId as target are deleted too. |
586 | |
587 | Returns \c true if the asynchronous call has been successfully dispatched. |
588 | |
589 | The success of the operation is returned in the \l deleteNodeFinished() signal. |
590 | |
591 | The following example code deletes a node and all references to it from the server: |
592 | |
593 | \code |
594 | m_client->deleteNode(QOpcUaExpandedNodeId("ns=3;s=MyNewVariableNode"), true); |
595 | \endcode |
596 | |
597 | \sa addNode() deleteNodeFinished() |
598 | */ |
599 | bool QOpcUaClient::deleteNode(const QString &nodeId, bool deleteTargetReferences) |
600 | { |
601 | if (state() != QOpcUaClient::Connected) |
602 | return false; |
603 | |
604 | Q_D(QOpcUaClient); |
605 | return d->m_impl->deleteNode(nodeId, deleteTargetReferences); |
606 | } |
607 | |
608 | /*! |
609 | Adds the reference described by \a referenceToAdd to the server. |
610 | |
611 | Returns \c true if the asynchronous call has been successfully dispatched. |
612 | |
613 | The success of the operation is returned in the \l addReferenceFinished() signal. |
614 | |
615 | The following example code adds a reference to a node to the "Objects" folder: |
616 | |
617 | \code |
618 | QOpcUaAddReferenceItem item; |
619 | item.setSourceNodeId(QOpcUa::namespace0Id(QOpcUa::NodeIds::Namespace0::ObjectsFolder)); |
620 | item.setReferenceTypeId(QOpcUa::nodeIdFromInteger(0, static_cast<quint32>(QOpcUa::ReferenceTypeId::Organizes))); |
621 | item.setIsForwardReference(true); |
622 | item.setTargetNodeId(QOpcUaExpandedNodeId("ns=3;s=MyNewVariableNode")); |
623 | item.setTargetNodeClass(QOpcUa::NodeClass::Variable); |
624 | |
625 | m_client->addReference(item); |
626 | \endcode |
627 | |
628 | \sa deleteReference() addReferenceFinished() QOpcUaAddReferenceItem |
629 | */ |
630 | bool QOpcUaClient::addReference(const QOpcUaAddReferenceItem &referenceToAdd) |
631 | { |
632 | if (state() != QOpcUaClient::Connected) |
633 | return false; |
634 | |
635 | Q_D(QOpcUaClient); |
636 | return d->m_impl->addReference(referenceToAdd); |
637 | } |
638 | |
639 | /*! |
640 | Deletes the reference described by \a referenceToDelete from the server. |
641 | |
642 | Returns \c true if the asynchronous call has been successfully dispatched. |
643 | |
644 | The success of the operation is returned in the \l deleteReferenceFinished() signal. |
645 | |
646 | The following example code deletes a reference to a node from the "Objects" folder: |
647 | |
648 | \code |
649 | QOpcUaDeleteReferenceItem item; |
650 | item.setSourceNodeId(QOpcUa::namespace0Id(QOpcUa::NodeIds::Namespace0::ObjectsFolder)); |
651 | item.setReferenceTypeId(QOpcUa::nodeIdFromInteger(0, static_cast<quint32>(QOpcUa::ReferenceTypeId::Organizes))); |
652 | item.setIsForwardReference(true); |
653 | item.setTargetNodeId(QOpcUaExpandedNodeId("ns=3;s=MyNewVariableNode")); |
654 | item.setDeleteBidirectional(true); |
655 | |
656 | m_client->deleteReference(item); |
657 | \endcode |
658 | |
659 | \sa addReference() deleteReferenceFinished() QOpcUaDeleteReferenceItem |
660 | */ |
661 | bool QOpcUaClient::deleteReference(const QOpcUaDeleteReferenceItem &referenceToDelete) |
662 | { |
663 | if (state() != QOpcUaClient::Connected) |
664 | return false; |
665 | |
666 | Q_D(QOpcUaClient); |
667 | return d->m_impl->deleteReference(referenceToDelete); |
668 | } |
669 | |
670 | /*! |
671 | Starts an asynchronous \c GetEndpoints request to read a list of available endpoints |
672 | from the server at \a url. |
673 | Returns \c true if the asynchronous call has been successfully dispatched. |
674 | |
675 | The endpoint information is returned in the \l endpointsRequestFinished() signal. |
676 | */ |
677 | bool QOpcUaClient::requestEndpoints(const QUrl &url) |
678 | { |
679 | Q_D(QOpcUaClient); |
680 | return d->m_impl->requestEndpoints(url); |
681 | } |
682 | |
683 | /*! |
684 | Starts an asynchronous FindServers request to read a list of known servers from a server or |
685 | discovery server at \a url. |
686 | Returns \c true if the asynchronous call has been successfully dispatched. |
687 | |
688 | \a localeIds can be used to select the language of the application names returned by the request. |
689 | The format is specified in OPC-UA part 3, 8.4, for example "en" for English, or "de-DE" for |
690 | German (Germany). If more than one locale ID is specified, the server uses the first match. If there |
691 | is no match or \a localeIds is empty, a default locale is chosen by the server. |
692 | |
693 | \a serverUris may be used to restrict the results to servers with a matching applicationUri in their |
694 | application description. For example, finding the current URL of the server with the applicationUri |
695 | "MyPLC", the following call can be used: |
696 | |
697 | \code |
698 | client->findServers(discoveryServerUrl, QStringList(), QStringList({"MyPLC"})); |
699 | \endcode |
700 | |
701 | The results are returned in the \l findServersFinished() signal. |
702 | */ |
703 | bool QOpcUaClient::findServers(const QUrl &url, const QStringList &localeIds, const QStringList &serverUris) |
704 | { |
705 | Q_D(QOpcUaClient); |
706 | return d->m_impl->findServers(url, localeIds, serverUris); |
707 | } |
708 | |
709 | /*! |
710 | Starts a read of multiple attributes on different nodes. |
711 | The node id, the attribute and an index range can be specified for every entry in \a nodesToRead. |
712 | |
713 | Returns true if the asynchronous request has been successfully dispatched. |
714 | The results are returned in the \l readNodeAttributesFinished() signal. |
715 | |
716 | This read function offers an alternative way to read attributes of nodes which can be used |
717 | for scenarios where the values of a large number of node attributes on different nodes must be read |
718 | without requiring the other features of the \l QOpcUaNode based API like monitoring for value changes. |
719 | All read items in the request are sent to the server in a single request and are answered in a single |
720 | response which generates a single \l readNodeAttributesFinished() signal. This reduces the network overhead and |
721 | the number of signal slot connections if many different nodes are involved. |
722 | |
723 | In the following example, the display name attribute and the two index ranges "0:2" and "5:7" of the value |
724 | attribute of the same node and the entire value attribute of a second node are read using a single service call: |
725 | \code |
726 | QList<QOpcUaReadItem> request; |
727 | request.push_back(QOpcUaReadItem("ns=1;s=MyArrayNode", |
728 | QOpcUa::NodeAttribute::DisplayName)); |
729 | request.push_back(QOpcUaReadItem("ns=1;s=MyArrayNode", |
730 | QOpcUa::NodeAttribute::Value, "0:2")); |
731 | request.push_back(QOpcUaReadItem("ns=1;s=MyArrayNode", |
732 | QOpcUa::NodeAttribute::Value, "5:7")); |
733 | request.push_back(QOpcUaReadItem("ns=1;s=MyScalarNode)); |
734 | m_client->readNodeAttributes(request); |
735 | \endcode |
736 | |
737 | \sa QOpcUaReadItem readNodeAttributesFinished() |
738 | */ |
739 | bool QOpcUaClient::readNodeAttributes(const QList<QOpcUaReadItem> &nodesToRead) |
740 | { |
741 | if (state() != QOpcUaClient::Connected) |
742 | return false; |
743 | |
744 | Q_D(QOpcUaClient); |
745 | return d->m_impl->readNodeAttributes(nodesToRead); |
746 | } |
747 | |
748 | /*! |
749 | Starts a write for multiple attributes on different nodes. |
750 | The node id, the attribute, the value, the value type and an index range can be specified |
751 | for every entry in \a nodesToWrite. |
752 | |
753 | Returns \c true if the asynchronous request has been successfully dispatched. |
754 | The results are returned in the \l writeNodeAttributesFinished() signal. |
755 | |
756 | This write function offers an alternative way to write attributes of nodes which can be used |
757 | for scenarios where the values of a large number of node attributes on different nodes must be written |
758 | without requiring the other features of the \l QOpcUaNode based API like monitoring for value changes. |
759 | All write items in the request are sent to the server in a single request and are answered in a single |
760 | response which generates a single \l writeNodeAttributesFinished() signal. This reduces the network overhead and |
761 | the number of signal slot connections if many different nodes are involved. |
762 | |
763 | In the following example, the Values attributes of two different nodes are written in one call. |
764 | The second node has an array value of which only the first two elements are overwritten: |
765 | |
766 | \code |
767 | QList<QOpcUaWriteItem> request; |
768 | |
769 | request.append(QOpcUaWriteItem("ns=2;s=Demo.Static.Scalar.Double", QOpcUa::NodeAttribute::Value, |
770 | 23.0, QOpcUa::Types::Double)); |
771 | request.append(QOpcUaWriteItem("ns=2;s=Demo.Static.Arrays.UInt32", QOpcUa::NodeAttribute::Value, |
772 | QVariantList({0, 1, 2}), QOpcUa::Types::UInt32, "0:2")); |
773 | |
774 | m_client->writeNodeAttributes(request); |
775 | \endcode |
776 | |
777 | \sa QOpcUaWriteItem writeNodeAttributesFinished() |
778 | */ |
779 | bool QOpcUaClient::writeNodeAttributes(const QList<QOpcUaWriteItem> &nodesToWrite) |
780 | { |
781 | if (state() != QOpcUaClient::Connected) |
782 | return false; |
783 | |
784 | Q_D(QOpcUaClient); |
785 | return d->m_impl->writeNodeAttributes(nodesToWrite); |
786 | } |
787 | |
788 | /*! |
789 | Returns the name of the backend used by this instance of QOpcUaClient, |
790 | e.g. "open62541". |
791 | */ |
792 | QString QOpcUaClient::backend() const |
793 | { |
794 | Q_D(const QOpcUaClient); |
795 | return d->m_impl->backend(); |
796 | } |
797 | |
798 | /*! |
799 | Enables automatic update of the namespace table. |
800 | |
801 | Enabling this will keep the local copy of the namespace table updated automatically. |
802 | \l namespaceArrayUpdated will be emitted when the array changed. |
803 | \a isEnabled determines if autoupdate is being enabled or disabled. |
804 | |
805 | A subscription will be made on the node on the server to keep track of changes. |
806 | In case a server does not support subscriptions this will not work and |
807 | \l isNamespaceAutoupdateEnabled returns \c false. |
808 | |
809 | \sa namespaceArray() namespaceArrayUpdated() |
810 | */ |
811 | void QOpcUaClient::setNamespaceAutoupdate(bool isEnabled) |
812 | { |
813 | Q_D(QOpcUaClient); |
814 | d->m_enableNamespaceArrayAutoupdate = isEnabled; |
815 | d->setupNamespaceArrayMonitoring(); |
816 | } |
817 | |
818 | /*! |
819 | Returns whether autoupdate of the namespace array is enabled. |
820 | */ |
821 | bool QOpcUaClient::isNamespaceAutoupdateEnabled() const |
822 | { |
823 | Q_D(const QOpcUaClient); |
824 | return d->m_enableNamespaceArrayAutoupdate; |
825 | } |
826 | |
827 | /*! |
828 | Sets the interval for the namespace table subscription. |
829 | |
830 | The subscription may be revised by the server. |
831 | |
832 | \a interval determines the interval to check for changes in milliseconds. The default is once per second. |
833 | |
834 | \sa QOpcUaClient::setNamespaceAutoupdate(bool isEnabled) |
835 | */ |
836 | void QOpcUaClient::setNamespaceAutoupdateInterval(int interval) |
837 | { |
838 | Q_D(QOpcUaClient); |
839 | d->m_namespaceArrayUpdateInterval = interval; |
840 | d->setupNamespaceArrayMonitoring(); |
841 | } |
842 | |
843 | /*! |
844 | Returns the current revised update interval of the namespace array. |
845 | |
846 | \sa setNamespaceAutoupdateInterval(int interval) |
847 | */ |
848 | int QOpcUaClient::namespaceAutoupdateInterval() const |
849 | { |
850 | Q_D(const QOpcUaClient); |
851 | return d->m_namespaceArrayUpdateInterval; |
852 | } |
853 | |
854 | /*! |
855 | Sets the authentication information of this client to \a authenticationInformation. |
856 | |
857 | \sa connectToEndpoint() |
858 | */ |
859 | void QOpcUaClient::setAuthenticationInformation(const QOpcUaAuthenticationInformation &authenticationInformation) |
860 | { |
861 | Q_D(QOpcUaClient); |
862 | d->m_authenticationInformation = authenticationInformation; |
863 | } |
864 | |
865 | /*! |
866 | Returns the current authentication information. |
867 | */ |
868 | const QOpcUaAuthenticationInformation &QOpcUaClient::authenticationInformation() const |
869 | { |
870 | Q_D(const QOpcUaClient); |
871 | return d->m_authenticationInformation; |
872 | } |
873 | |
874 | /*! |
875 | \since 6.6 |
876 | |
877 | Sets the connection settings for this client to \a connectionSettings. |
878 | |
879 | Example: |
880 | \code |
881 | QOpcUaConnectionSettings settings; |
882 | // Ask the server to give localized texts in german with french as fallback |
883 | settings.setSessionLocaleIds({ "de", "fr" }); |
884 | // We need to call some long running methods, increase the request timeout |
885 | settings.setRequestTimeout(std::chrono::minutes(2)); |
886 | opcuaClient->setConnectionSettings(settings); |
887 | \endcode |
888 | |
889 | The values from \a connectionSettings are applied to any new connections after this point. |
890 | |
891 | \sa connectionSettings() |
892 | */ |
893 | void QOpcUaClient::setConnectionSettings(const QOpcUaConnectionSettings &connectionSettings) |
894 | { |
895 | Q_D(QOpcUaClient); |
896 | d->m_connectionSettings = connectionSettings; |
897 | } |
898 | |
899 | /*! |
900 | \since 6.6 |
901 | |
902 | Returns the connection settings for this client. |
903 | |
904 | \sa setConnectionSettings() |
905 | */ |
906 | QOpcUaConnectionSettings QOpcUaClient::connectionSettings() const |
907 | { |
908 | Q_D(const QOpcUaClient); |
909 | return d->m_connectionSettings; |
910 | } |
911 | |
912 | /*! |
913 | \since QtOpcUa 5.14 |
914 | |
915 | Returns the security policies supported by the used backend. |
916 | |
917 | This function is currently available as a Technology Preview, and therefore the API |
918 | and functionality provided by the function may be subject to change at any time without |
919 | prior notice. |
920 | */ |
921 | QStringList QOpcUaClient::supportedSecurityPolicies() const |
922 | { |
923 | Q_D(const QOpcUaClient); |
924 | return d->m_impl->supportedSecurityPolicies(); |
925 | } |
926 | |
927 | /*! |
928 | \since QtOpcUa 5.14 |
929 | |
930 | Returns the user token types supported by the used backend. |
931 | |
932 | This function is currently available as a Technology Preview, and therefore the API |
933 | and functionality provided by the function may be subject to change at any time without |
934 | prior notice. |
935 | |
936 | \sa QOpcUaUserTokenPolicy::TokenType |
937 | */ |
938 | QList<QOpcUaUserTokenPolicy::TokenType> QOpcUaClient::supportedUserTokenTypes() const |
939 | { |
940 | Q_D(const QOpcUaClient); |
941 | return d->m_impl->supportedUserTokenTypes(); |
942 | } |
943 | |
944 | /*! |
945 | \since 6.3 |
946 | |
947 | Starts a read raw history \a request for one or multiple nodes. This is the Qt OPC UA representation for the OPC UA |
948 | ReadHistory service for reading raw historical data defined in |
949 | \l {https://reference.opcfoundation.org/v104/Core/docs/Part4/5.10.3/} {OPC-UA part 4, 5.10.3}. |
950 | |
951 | The start timestamp, end timestamp, number of values per node, returnBounds and nodes to read |
952 | can be specified in a \l QOpcUaHistoryReadRawRequest. |
953 | |
954 | Returns a \l QOpcUaHistoryReadResponse which contains the state of the request if the asynchronous |
955 | request has been successfully dispatched. The results are returned in the |
956 | \l QOpcUaHistoryReadResponse::readHistoryDataFinished(const QList<QOpcUaHistoryData> &results, QOpcUa::UaStatusCode serviceResult) |
957 | signal. |
958 | |
959 | In the following example, the historic data from the last two days of two nodes are requested and printed. |
960 | The result is limited to ten values per node. |
961 | |
962 | \code |
963 | QOpcUaHistoryReadRawRequest request( |
964 | { QOpcUaReadItem("ns=1;s=myValue1"), QOpcUaReadItem("ns=1;s=myValue2") }, |
965 | QDateTime::currentDateTime(), |
966 | QDateTime::currentDateTime().addDays(-2), |
967 | 10, |
968 | true); |
969 | |
970 | QOpcUaHistoryReadResponse *response = m_client->readHistoryData(request); |
971 | if (response) { |
972 | QObject::connect(response, &QOpcUaHistoryReadResponse::readHistoryDataFinished, |
973 | [] (QList<QOpcUaHistoryData> results, QOpcUa::UaStatusCode serviceResult) { |
974 | if (serviceResult != QOpcUa::UaStatusCode::Good) { |
975 | qWarning() << "Fetching historical data failed with:" << serviceResult; |
976 | } else { |
977 | for (const auto& result : results) { |
978 | qInfo() << "NodeId:" << result.nodeId(); |
979 | for (const auto &dataValue : result.result()) |
980 | qInfo() << "Value:" << dataValue.value(); |
981 | } |
982 | } |
983 | }); |
984 | } |
985 | \endcode |
986 | */ |
987 | QOpcUaHistoryReadResponse *QOpcUaClient::readHistoryData(const QOpcUaHistoryReadRawRequest &request) |
988 | { |
989 | Q_D(const QOpcUaClient); |
990 | return d->m_impl->readHistoryData(request); |
991 | } |
992 | |
993 | QT_END_NAMESPACE |
994 | |