| 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 | |
| 17 | QT_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 | |
| 223 | Q_DECLARE_LOGGING_CATEGORY(QT_OPCUA_PLUGINS_QML) |
| 224 | |
| 225 | OpcUaConnection* OpcUaConnection::m_defaultConnection = nullptr; |
| 226 | |
| 227 | OpcUaConnection::OpcUaConnection(QObject *parent): |
| 228 | QObject(parent) |
| 229 | { |
| 230 | } |
| 231 | |
| 232 | OpcUaConnection::~OpcUaConnection() |
| 233 | { |
| 234 | setDefaultConnection(false); |
| 235 | if (m_client) { |
| 236 | m_client->disconnect(receiver: this); |
| 237 | delete m_client; |
| 238 | } |
| 239 | } |
| 240 | |
| 241 | QStringList OpcUaConnection::availableBackends() const |
| 242 | { |
| 243 | return QOpcUaProvider::availableBackends(); |
| 244 | } |
| 245 | |
| 246 | bool OpcUaConnection::connected() const |
| 247 | { |
| 248 | return m_connected && m_client; |
| 249 | } |
| 250 | |
| 251 | void 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 | |
| 280 | QString OpcUaConnection::backend() const |
| 281 | { |
| 282 | if (m_client) |
| 283 | return m_client->backend(); |
| 284 | else |
| 285 | return QString(); |
| 286 | } |
| 287 | |
| 288 | OpcUaConnection *OpcUaConnection::defaultConnection() |
| 289 | { |
| 290 | return m_defaultConnection; |
| 291 | } |
| 292 | |
| 293 | bool 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 | |
| 306 | void 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 | |
| 320 | void OpcUaConnection::disconnectFromEndpoint() |
| 321 | { |
| 322 | if (!m_client) |
| 323 | return; |
| 324 | |
| 325 | m_client->disconnectFromEndpoint(); |
| 326 | } |
| 327 | |
| 328 | void 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 | |
| 339 | void 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 | |
| 349 | QStringList OpcUaConnection::namespaces() const |
| 350 | { |
| 351 | if (!m_client) |
| 352 | return QStringList(); |
| 353 | |
| 354 | return m_client->namespaceArray(); |
| 355 | } |
| 356 | |
| 357 | QOpcUaEndpointDescription OpcUaConnection::currentEndpoint() const |
| 358 | { |
| 359 | if (!m_client || !m_connected) |
| 360 | return QOpcUaEndpointDescription(); |
| 361 | |
| 362 | return m_client->endpoint(); |
| 363 | } |
| 364 | |
| 365 | void OpcUaConnection::setAuthenticationInformation(const QOpcUaAuthenticationInformation &authenticationInformation) |
| 366 | { |
| 367 | if (!m_client) |
| 368 | return; |
| 369 | m_client->setAuthenticationInformation(authenticationInformation); |
| 370 | } |
| 371 | |
| 372 | void 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 | |
| 382 | QOpcUaAuthenticationInformation 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 | */ |
| 429 | bool 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 | */ |
| 514 | bool 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 | |
| 566 | QStringList OpcUaConnection::supportedSecurityPolicies() const |
| 567 | { |
| 568 | if (!m_client) |
| 569 | return QStringList(); |
| 570 | return m_client->supportedSecurityPolicies(); |
| 571 | } |
| 572 | |
| 573 | QJSValue 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 | |
| 590 | QOpcUaClient *OpcUaConnection::connection() const |
| 591 | { |
| 592 | return m_client; |
| 593 | } |
| 594 | |
| 595 | void 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 | |
| 605 | void 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 | |
| 615 | void 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 | |
| 625 | void 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 | |
| 643 | QT_END_NAMESPACE |
| 644 | |