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

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