1// Copyright (C) 2019 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include <private/opcuaconnection_p.h>
5#include <private/opcuareadresult_p.h>
6#include <private/opcuawriteitem_p.h>
7#include <private/opcuawriteresult_p.h>
8#include <private/universalnode_p.h>
9
10#include <QJSEngine>
11#include <QLoggingCategory>
12#include <QOpcUaProvider>
13#include <QOpcUaReadItem>
14#include <QOpcUaReadResult>
15#include <QOpcUaWriteItem>
16
17QT_BEGIN_NAMESPACE
18
19/*!
20 \qmltype Connection
21 \inqmlmodule QtOpcUa
22 \brief Connects to a server.
23 \since QtOpcUa 5.12
24
25 The main API uses backends to make connections. You have to set the backend before
26 any connection attempt.
27
28 \code
29 import QtOpcUa as QtOpcUa
30
31 QtOpcUa.Connection {
32 backend: "open62541"
33 }
34
35 Component.onCompleted: {
36 connection.connectToEndpoint("opc.tcp://127.0.0.1:43344");
37 }
38 \endcode
39*/
40
41/*!
42 \qmlproperty stringlist Connection::availableBackends
43 \readonly
44
45 Returns the names of all available backends as a list.
46 These are used to select a backend when connecting.
47
48 \sa Connection::backend
49*/
50
51/*!
52 \qmlproperty bool Connection::connected
53 \readonly
54
55 Status of the connection.
56 \c true when there is a connection, otherwise \c false.
57*/
58
59/*!
60 \qmlproperty string Connection::backend
61
62 Set the backend to use for a connection to the server.
63 Has to be set before any connection attempt.
64
65 \sa Connection::availableBackends
66*/
67
68/*!
69 \qmlproperty bool Connection::defaultConnection
70
71 Makes this the default connection.
72 Usually each node needs to be given a connection to use. If this property
73 is set to \c true, this connection will be used in all cases where a node has no
74 connection set. Already established connections are not affected.
75 If \c defaultConnection is set to \c true on multiple connection the last one is used.
76
77 \code
78 QtOpcUa.Connection {
79 ...
80 defaultConnection: true
81 ...
82 }
83 \endcode
84
85 \sa Node
86*/
87
88/*!
89 \qmlproperty stringlist Connection::namespaces
90 \readonly
91
92 List of strings of all namespace URIs registered on the connected server.
93*/
94
95/*!
96 \qmlproperty AuthenticationInformation Connection::authenticationInformation
97
98 Set the authentication information to this connection. The authentication information has
99 to be set before calling \l connectToEndpoint. If no authentication information is set,
100 the anonymous mode will be used.
101 It has no effect on the current connection. If the client is disconnected and then reconnected,
102 the new credentials are used.
103 Reading and writing this property before a \l backend is set, writes are ignored and reads return
104 and invalid \l AuthenticationInformation.
105*/
106
107/*!
108 \qmlproperty stringlist Connection::supportedSecurityPolicies
109 \since 5.13
110
111 A list of strings containing the supported security policies
112
113 This property is currently available as a Technology Preview, and therefore the API
114 and functionality provided may be subject to change at any time without
115 prior notice.
116*/
117
118/*!
119 \qmlproperty array[tokenTypes] Connection::supportedUserTokenTypes
120 \since 5.13
121
122 An array of user token policy types of all supported user token types.
123
124 This property is currently available as a Technology Preview, and therefore the API
125 and functionality provided may be subject to change at any time without
126 prior notice.
127*/
128
129/*!
130 \qmlproperty QOpcUaEndpointDescription Connection::currentEndpoint
131 \since 5.13
132
133 An endpoint description of the server to which the connection is connected to.
134 When the connection is not established, an empty endpoint description is returned.
135*/
136
137/*!
138 \qmlsignal Connection::readNodeAttributesFinished(readResults)
139 \since 5.13
140
141 Emitted when the read request, started using \l readNodeAttributes(), is finished.
142 The \a readResults parameter is an array of \l ReadResult entries, containing the
143 values requested from the server.
144
145 \code
146 connection.onReadNodeAttributesFinished(results) {
147 for (var i = 0; results.length; i++) {
148 if (results[i].status.isGood) {
149 console.log(results[i].value);
150 } else {
151 // handle error
152 }
153 }
154 }
155 \endcode
156
157 \sa readNodeAttributes(), ReadResult
158*/
159
160/*!
161 \qmlsignal Connection::writeNodeAttributesFinished(writeResults)
162 \since 5.13
163
164 Emitted when the write request started using \l writeNodeAttributes() is
165 finished. The \a writeResults parameter is an array of \l WriteResult entries,
166 containing the values requested from the server.
167
168 \code
169 for (var i = 0; i < writeResults.length; i++) {
170 console.log(writeResults[i].nodeId);
171 console.log(writeResults[i].namespaceName);
172 console.log(writeResults[i].attribute);
173
174 if (writeResults[i].status.isBad) {
175 // value was not written
176 }
177 }
178 \endcode
179
180 \sa writeNodeAttributes(), WriteResult
181
182*/
183
184/*!
185 \qmlproperty QOpcUaClient Connection::connection
186 \since 5.13
187
188 This property is used only to inject a connection from C++. In case of complex setup of
189 a connection you can use C++ to handle all the details. After the connection is established
190 it can be handed to QML using this property. Ownership of the client is transferred to QML.
191
192 \code
193 class MyClass : public QObject {
194 Q_OBJECT
195 Q_PROPERTY(QOpcUaClient* connection READ connection NOTIFY connectionChanged)
196
197 public:
198 MyClass (QObject* parent = nullptr);
199 QOpcUaClient *connection() const;
200
201 signals:
202 void connectionChanged(QOpcUaClient *);
203 \endcode
204
205 Emitting the signal \c connectionChanged when the client setup is completed, the QML code below will
206 use the connection.
207
208 \code
209 import QtOpcUa as QtOpcUa
210
211 MyClass {
212 id: myclass
213 }
214
215 QtOpcUa.Connection {
216 connection: myclass.connection
217 }
218 \endcode
219
220*/
221
222
223Q_DECLARE_LOGGING_CATEGORY(QT_OPCUA_PLUGINS_QML)
224
225OpcUaConnection* OpcUaConnection::m_defaultConnection = nullptr;
226
227OpcUaConnection::OpcUaConnection(QObject *parent):
228 QObject(parent)
229{
230}
231
232OpcUaConnection::~OpcUaConnection()
233{
234 setDefaultConnection(false);
235 if (m_client) {
236 m_client->disconnect(receiver: this);
237 delete m_client;
238 }
239}
240
241QStringList OpcUaConnection::availableBackends() const
242{
243 return QOpcUaProvider::availableBackends();
244}
245
246bool OpcUaConnection::connected() const
247{
248 return m_connected && m_client;
249}
250
251void OpcUaConnection::setBackend(const QString &name)
252{
253 if (name.isEmpty())
254 return;
255
256 if (!availableBackends().contains(str: name)) {
257 qCWarning(QT_OPCUA_PLUGINS_QML) << tr(s: "Backend '%1' is not available").arg(a: name);
258 qCDebug(QT_OPCUA_PLUGINS_QML) << tr(s: "Available backends:") << availableBackends().join(sep: QLatin1Char(','));
259 return;
260 }
261
262 if (m_client) {
263 if (m_client->backend() == name)
264 return;
265
266 removeConnection();
267 }
268
269 QOpcUaProvider provider;
270 m_client = provider.createClient(backend: name);
271 if (m_client) {
272 qCDebug(QT_OPCUA_PLUGINS_QML) << "Created plugin" << m_client->backend();
273 setupConnection();
274 } else {
275 qCWarning(QT_OPCUA_PLUGINS_QML) << tr(s: "Backend '%1' could not be created.").arg(a: name);
276 }
277 emit backendChanged();
278}
279
280QString OpcUaConnection::backend() const
281{
282 if (m_client)
283 return m_client->backend();
284 else
285 return QString();
286}
287
288OpcUaConnection *OpcUaConnection::defaultConnection()
289{
290 return m_defaultConnection;
291}
292
293bool OpcUaConnection::isDefaultConnection() const
294{
295 return m_defaultConnection == this;
296}
297
298/*!
299 \qmlmethod Connection::connectToEndpoint(endpointDescription)
300
301 Connects to the endpoint specified with \a endpointDescription.
302
303 \sa EndpointDescription
304*/
305
306void OpcUaConnection::connectToEndpoint(const QOpcUaEndpointDescription &endpointDescription)
307{
308 if (!m_client)
309 return;
310
311 m_client->connectToEndpoint(endpoint: endpointDescription);
312}
313
314/*!
315 \qmlmethod Connection::disconnectFromEndpoint()
316
317 Disconnects an established connection.
318*/
319
320void OpcUaConnection::disconnectFromEndpoint()
321{
322 if (!m_client)
323 return;
324
325 m_client->disconnectFromEndpoint();
326}
327
328void OpcUaConnection::setDefaultConnection(bool defaultConnection)
329{
330 if (!defaultConnection && m_defaultConnection == this)
331 m_defaultConnection = nullptr;
332
333 if (defaultConnection)
334 m_defaultConnection = this;
335
336 emit defaultConnectionChanged();
337}
338
339void OpcUaConnection::clientStateHandler(QOpcUaClient::ClientState state)
340{
341 if (m_connected) {
342 // don't immediately send the state; we have to wait for the namespace
343 // array to be updated
344 m_connected = (state == QOpcUaClient::ClientState::Connected);
345 emit connectedChanged();
346 }
347}
348
349QStringList OpcUaConnection::namespaces() const
350{
351 if (!m_client)
352 return QStringList();
353
354 return m_client->namespaceArray();
355}
356
357QOpcUaEndpointDescription OpcUaConnection::currentEndpoint() const
358{
359 if (!m_client || !m_connected)
360 return QOpcUaEndpointDescription();
361
362 return m_client->endpoint();
363}
364
365void OpcUaConnection::setAuthenticationInformation(const QOpcUaAuthenticationInformation &authenticationInformation)
366{
367 if (!m_client)
368 return;
369 m_client->setAuthenticationInformation(authenticationInformation);
370}
371
372void OpcUaConnection::setConnection(QOpcUaClient *client)
373{
374 if (!client)
375 return;
376 removeConnection();
377 m_client = client;
378 m_client->setParent(nullptr);
379 setupConnection();
380}
381
382QOpcUaAuthenticationInformation OpcUaConnection::authenticationInformation() const
383{
384 if (!m_client)
385 return QOpcUaAuthenticationInformation();
386
387 return m_client->authenticationInformation();
388}
389
390/*!
391 \qmlmethod Connection::readNodeAttributes(valuesToBeRead)
392
393 This function is used to read multiple values from a server in one go.
394 Returns \c true if the read request was dispatched successfully.
395
396 The \a valuesToBeRead parameter must be a JavaScript array of \l ReadItem
397 entries.
398
399 \code
400 // List of items to read
401 var readItemList = [];
402 // Item to be added to the list of items to be read
403 var readItem;
404
405 // Prepare an item to be read
406
407 // Create a new read item and fill properties
408 readItem = QtOpcUa.ReadItem.create();
409 readItem.ns = "http://qt-project.org";
410 readItem.nodeId = "s=Demo.Static.Scalar.Double";
411 readItem.attribute = QtOpcUa.Constants.NodeAttribute.DisplayName;
412
413 // Add the prepared item to the list of items to be read
414 readItemList.push(readItem);
415
416 // Add further items
417 [...]
418
419 if (!connection.readNodeAttributes(readItemList)) {
420 // handle error
421 }
422 \endcode
423
424 The result of the read request are provided by the signal
425 \l readNodeAttributesFinished().
426
427 \sa readNodeAttributesFinished(), ReadItem
428*/
429bool OpcUaConnection::readNodeAttributes(const QJSValue &value)
430{
431 if (!m_client || !m_connected) {
432 qCWarning(QT_OPCUA_PLUGINS_QML) << tr(s: "Not connected to server.");
433 return false;
434 }
435 if (!value.isArray()) {
436 qCWarning(QT_OPCUA_PLUGINS_QML) << tr(s: "List of ReadItems it not an array.");
437 return false;
438 }
439
440 QList<QOpcUaReadItem> readItemList;
441
442 for (int i = 0, end = value.property(QStringLiteral("length")).toInt(); i < end; ++i){
443 const auto &readItem = qjsvalue_cast<OpcUaReadItem>(value: value.property(arrayIndex: i));
444 if (readItem.nodeId().isEmpty()) {
445 qCWarning(QT_OPCUA_PLUGINS_QML) << tr(s: "Invalid ReadItem in list of items at index %1").arg(a: i);
446 return false;
447 }
448
449 QString finalNode;
450 bool ok;
451 int index = readItem.namespaceIdentifier().toInt(ok: &ok);
452 if (ok) {
453 QString identifier;
454 UniversalNode::splitNodeIdAndNamespace(nodeIdentifier: readItem.nodeId(), namespaceIndex: nullptr, identifier: &identifier);
455 finalNode = UniversalNode::createNodeString(namespaceIndex: index, nodeIdentifier: identifier);
456 } else {
457 finalNode = UniversalNode::resolveNamespaceToNode(nodeId: readItem.nodeId(), namespaceName: readItem.namespaceIdentifier().toString(), client: m_client);
458 }
459
460 if (finalNode.isEmpty()) {
461 qCWarning(QT_OPCUA_PLUGINS_QML) << tr(s: "Failed to resolve node.");
462 return false;
463 }
464 readItemList.push_back(t: QOpcUaReadItem(finalNode,
465 readItem.attribute(),
466 readItem.indexRange())
467 );
468 }
469
470 return m_client->readNodeAttributes(nodesToRead: readItemList);
471}
472
473/*!
474 \qmlmethod Connection::writeNodeAttributes(valuesToBeWritten)
475
476 This function is used to write multiple values to a server in one go.
477 Returns \c true if the write request was dispatched successfully.
478
479 The \a valuesToBeWritten parameter must be a JavaScript array of
480 \l WriteItem entries.
481
482 \code
483 // List of items to write
484 var writeItemList = [];
485 // Item to be added to the list of items to be written
486 var writeItem;
487
488 // Prepare an item to be written
489
490 // Create a new write item and fill properties
491 writeItem = QtOpcUa.WriteItem.create();
492 writeItem.ns = "http://qt-project.org";
493 writeItem.nodeId = "s=Demo.Static.Scalar.Double";
494 writeItem.attribute = QtOpcUa.Constants.NodeAttribute.Value;
495 writeItem.value = 32.1;
496 writeItem.valueType = QtOpcUa.Constants.Double;
497
498 // Add the prepared item to the list of items to be written
499 writeItemList.push(writeItem);
500
501 // Add further items
502 [...]
503
504 if (!connection.writeNodeAttributes(writeItemList)) {
505 // handle error
506 }
507 \endcode
508
509 The result of the write request are provided by the signal
510 \l Connection::writeNodeAttributesFinished().
511
512 \sa Connection::writeNodeAttributesFinished() WriteItem
513*/
514bool OpcUaConnection::writeNodeAttributes(const QJSValue &value)
515{
516 if (!m_client || !m_connected) {
517 qCWarning(QT_OPCUA_PLUGINS_QML) << tr(s: "Not connected to server.");
518 return false;
519 }
520 if (!value.isArray()) {
521 qCWarning(QT_OPCUA_PLUGINS_QML) << tr(s: "List of WriteItems it not an array.");
522 return false;
523 }
524
525 QList<QOpcUaWriteItem> writeItemList;
526
527 for (int i = 0, end = value.property(QStringLiteral("length")).toInt(); i < end; ++i) {
528 const auto &writeItem = qjsvalue_cast<OpcUaWriteItem>(value: value.property(arrayIndex: i));
529 if (writeItem.nodeId().isEmpty()) {
530 qCWarning(QT_OPCUA_PLUGINS_QML) << tr(s: "Invalid WriteItem in list of items at index %1").arg(a: i);
531 return false;
532 }
533
534 QString finalNode;
535 bool ok;
536 int index = writeItem.namespaceIdentifier().toInt(ok: &ok);
537 if (ok) {
538 QString identifier;
539 UniversalNode::splitNodeIdAndNamespace(nodeIdentifier: writeItem.nodeId(), namespaceIndex: nullptr, identifier: &identifier);
540 finalNode = UniversalNode::createNodeString(namespaceIndex: index, nodeIdentifier: identifier);
541 } else {
542 finalNode = UniversalNode::resolveNamespaceToNode(nodeId: writeItem.nodeId(), namespaceName: writeItem.namespaceIdentifier().toString(), client: m_client);
543 }
544
545 if (finalNode.isEmpty()) {
546 qCWarning(QT_OPCUA_PLUGINS_QML) << tr(s: "Failed to resolve node.");
547 return false;
548 }
549
550 auto tmp = QOpcUaWriteItem(finalNode,
551 writeItem.attribute(),
552 writeItem.value(),
553 writeItem.valueType(),
554 writeItem.indexRange());
555
556 tmp.setSourceTimestamp(writeItem.sourceTimestamp());
557 tmp.setServerTimestamp(writeItem.serverTimestamp());
558 if (writeItem.hasStatusCode())
559 tmp.setStatusCode(static_cast<QOpcUa::UaStatusCode>(writeItem.statusCode()));
560 writeItemList.push_back(t: tmp);
561 }
562
563 return m_client->writeNodeAttributes(nodesToWrite: writeItemList);
564}
565
566QStringList OpcUaConnection::supportedSecurityPolicies() const
567{
568 if (!m_client)
569 return QStringList();
570 return m_client->supportedSecurityPolicies();
571}
572
573QJSValue OpcUaConnection::supportedUserTokenTypes() const
574{
575 if (!m_client)
576 return QJSValue();
577
578 auto engine = qjsEngine(this);
579 if (!engine)
580 return QJSValue();
581
582 const auto tokenTypes = m_client->supportedUserTokenTypes();
583 auto returnValue = engine->newArray(length: tokenTypes.size());
584 for (int i = 0; i < tokenTypes.size(); ++i)
585 returnValue.setProperty(arrayIndex: i, value: tokenTypes[i]);
586
587 return returnValue;
588}
589
590QOpcUaClient *OpcUaConnection::connection() const
591{
592 return m_client;
593}
594
595void OpcUaConnection::handleReadNodeAttributesFinished(const QList<QOpcUaReadResult> &results)
596{
597 QVariantList returnValue;
598
599 for (const auto &result : results)
600 returnValue.append(t: QVariant::fromValue(value: OpcUaReadResult(result, m_client)));
601
602 emit readNodeAttributesFinished(value: QVariant::fromValue(value: returnValue));
603}
604
605void OpcUaConnection::handleWriteNodeAttributesFinished(const QList<QOpcUaWriteResult> &results)
606{
607 QVariantList returnValue;
608
609 for (const auto &result : results)
610 returnValue.append(t: QVariant::fromValue(value: OpcUaWriteResult(result, m_client)));
611
612 emit writeNodeAttributesFinished(value: QVariant::fromValue(value: returnValue));
613}
614
615void OpcUaConnection::removeConnection()
616{
617 if (m_client) {
618 m_client->disconnect(receiver: this);
619 m_client->disconnectFromEndpoint();
620 m_client->deleteLater();
621 m_client = nullptr;
622 }
623}
624
625void OpcUaConnection::setupConnection()
626{
627 connect(sender: m_client, signal: &QOpcUaClient::stateChanged, context: this, slot: &OpcUaConnection::clientStateHandler);
628 connect(sender: m_client, signal: &QOpcUaClient::namespaceArrayUpdated, context: this, slot: &OpcUaConnection::namespacesChanged);
629 connect(sender: m_client, signal: &QOpcUaClient::namespaceArrayUpdated, context: this, slot: [&]() {
630 if (!m_connected) {
631 m_connected = true;
632 emit connectedChanged();
633 }
634 });
635 m_client->setNamespaceAutoupdate(true);
636 connect(sender: m_client, signal: &QOpcUaClient::readNodeAttributesFinished, context: this, slot: &OpcUaConnection::handleReadNodeAttributesFinished);
637 connect(sender: m_client, signal: &QOpcUaClient::writeNodeAttributesFinished, context: this, slot: &OpcUaConnection::handleWriteNodeAttributesFinished);
638 m_connected = (!m_client->namespaceArray().isEmpty() && m_client->state() == QOpcUaClient::Connected);
639 if (m_connected)
640 emit connectedChanged();
641}
642
643QT_END_NAMESPACE
644

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

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