/****************************************************************************** ** ** Copyright (C) 2019 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtOpcUa module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:GPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 or (at your option) any later version ** approved by the KDE Free Qt Foundation. The licenses are as published by ** the Free Software Foundation and appearing in the file LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ******************************************************************************/ #include "quacppbackend.h" #include "quacppclient.h" #include "quacppsubscription.h" #include "quacpputils.h" #include "quacppvalueconverter.h" #include "qopcuaauthenticationinformation.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // We only undef max and do not use NOMINMAX, as the UA SDK seems to rely on it. #ifdef max #undef max #endif quint32 UACppAsyncBackend::m_numClients = 0; bool UACppAsyncBackend::m_platformLayerInitialized = false; using namespace UaClientSdk; QT_BEGIN_NAMESPACE Q_DECLARE_LOGGING_CATEGORY(QT_OPCUA_PLUGINS_UACPP) UACppAsyncBackend::UACppAsyncBackend(QUACppClient *parent) : QOpcUaBackend() , m_clientImpl(parent) , m_minPublishingInterval(0) { QMutexLocker locker(&m_lifecycleMutex); if (!m_platformLayerInitialized) { UaPlatformLayer::init(); m_platformLayerInitialized = true; } m_numClients++; m_nativeSession = new UaSession(); } UACppAsyncBackend::~UACppAsyncBackend() { cleanupSubscriptions(); if (m_nativeSession) { if (m_nativeSession->isConnected() != OpcUa_False) { if (m_nativeSession->serverStatus() != UaClient::ConnectionErrorApiReconnect) qCWarning(QT_OPCUA_PLUGINS_UACPP) << "UACPP: Deleting backend while still connected"; ServiceSettings serviceSettings; m_nativeSession->disconnect(serviceSettings, OpcUa_True); } qCDebug(QT_OPCUA_PLUGINS_UACPP) << "UACPP: Deleting session"; delete m_nativeSession; } QMutexLocker locker(&m_lifecycleMutex); m_numClients--; if (!m_numClients && m_platformLayerInitialized) { UaPlatformLayer::cleanup(); m_platformLayerInitialized = false; } } void UACppAsyncBackend::connectionStatusChanged(OpcUa_UInt32 clientConnectionId, UaClientSdk::UaClient::ServerStatus serverStatus) { Q_UNUSED(clientConnectionId); switch (serverStatus) { case UaClient::Disconnected: qCDebug(QT_OPCUA_PLUGINS_UACPP) << "Connection closed"; emit stateAndOrErrorChanged(QOpcUaClient::Disconnected, QOpcUaClient::NoError); cleanupSubscriptions(); break; case UaClient::Connected: qCDebug(QT_OPCUA_PLUGINS_UACPP) << "Connection established"; emit stateAndOrErrorChanged(QOpcUaClient::Connected, QOpcUaClient::NoError); break; case UaClient::ConnectionWarningWatchdogTimeout: qCWarning(QT_OPCUA_PLUGINS_UACPP) << "Unimplemented: Connection status changed to ConnectionWarningWatchdogTimeout"; break; case UaClient::ConnectionErrorApiReconnect: qCDebug(QT_OPCUA_PLUGINS_UACPP) << "Connection status changed to ConnectionErrorApiReconnect"; emit stateAndOrErrorChanged(QOpcUaClient::Disconnected, QOpcUaClient::ConnectionError); cleanupSubscriptions(); break; case UaClient::ServerShutdown: qCWarning(QT_OPCUA_PLUGINS_UACPP) << "Unimplemented: Connection status changed to ServerShutdown"; break; case UaClient::NewSessionCreated: qCWarning(QT_OPCUA_PLUGINS_UACPP) << "Unimplemented: Connection status changed to NewSessionCreated"; break; } } bool UACppAsyncBackend::connectError(OpcUa_UInt32 clientConnectionId, UaClientSdk::UaClient::ConnectServiceType serviceType, const UaStatus &error, bool clientSideError) { Q_UNUSED(clientConnectionId); QOpcUaErrorState errorState; errorState.setConnectionStep(connectionStepFromUaServiceType(serviceType)); errorState.setErrorCode(static_cast(error.statusCode())); errorState.setClientSideError(clientSideError); errorState.setIgnoreError(false); // This signal is connected using Qt::BlockingQueuedConnection. It will place a metacall to a different thread and waits // until this metacall is fully handled before returning. emit QOpcUaBackend::connectError(&errorState); return errorState.ignoreError(); } void UACppAsyncBackend::browse(quint64 handle, const UaNodeId &id, const QOpcUaBrowseRequest &request) { UaStatus status; ServiceSettings serviceSettings; BrowseContext browseContext; UaByteString continuationPoint; UaReferenceDescriptions referenceDescriptions; browseContext.referenceTypeId = UACppUtils::nodeIdFromQString(request.referenceTypeId()); browseContext.nodeClassMask = request.nodeClassMask(); browseContext.includeSubtype = request.includeSubtypes(); browseContext.browseDirection = static_cast(request.browseDirection()); QStringList result; QVector ret; status = m_nativeSession->browse(serviceSettings, id, browseContext, continuationPoint, referenceDescriptions); bool initialBrowse = true; do { if (!initialBrowse) status = m_nativeSession->browseNext(serviceSettings, OpcUa_False, continuationPoint, referenceDescriptions); if (status.isBad()) { qCWarning(QT_OPCUA_PLUGINS_UACPP) << "Could not browse children."; break; } initialBrowse = false; for (quint32 i = 0; i < referenceDescriptions.length(); ++i) { const UaNodeId id(referenceDescriptions[i].NodeId.NodeId); const UaString uastr(id.toXmlString()); result.append(QString::fromUtf8(uastr.toUtf8(), uastr.size())); QOpcUaReferenceDescription temp; QOpcUaExpandedNodeId expandedId; expandedId.setNamespaceUri(QString::fromUtf8(UaString(referenceDescriptions[i].NodeId.NamespaceUri).toUtf8())); expandedId.setServerIndex(referenceDescriptions[i].NodeId.ServerIndex); expandedId.setNodeId(UACppUtils::nodeIdToQString(referenceDescriptions[i].NodeId.NodeId)); temp.setTargetNodeId(expandedId); expandedId.setNamespaceUri(QString::fromUtf8(UaString(referenceDescriptions[i].TypeDefinition.NamespaceUri).toUtf8())); expandedId.setServerIndex(referenceDescriptions[i].TypeDefinition.ServerIndex); expandedId.setNodeId(UACppUtils::nodeIdToQString(referenceDescriptions[i].TypeDefinition.NodeId)); temp.setTypeDefinition(expandedId); temp.setRefTypeId(UACppUtils::nodeIdToQString(UaNodeId(referenceDescriptions[i].ReferenceTypeId))); temp.setNodeClass(static_cast(referenceDescriptions[i].NodeClass)); temp.setBrowseName(QUACppValueConverter::scalarToQVariant( &referenceDescriptions[i].BrowseName, QMetaType::Type::UnknownType).value()); temp.setDisplayName(QUACppValueConverter::scalarToQVariant( &referenceDescriptions[i].DisplayName, QMetaType::Type::UnknownType).value()); temp.setIsForwardReference(referenceDescriptions[i].IsForward); ret.append(temp); } } while (continuationPoint.length() > 0); emit browseFinished(handle, ret, static_cast(status.statusCode())); } void UACppAsyncBackend::connectToEndpoint(const QOpcUaEndpointDescription &endpoint) { if (m_nativeSession->isConnected()) disconnectFromEndpoint(); QString errorMessage; if (!verifyEndpointDescription(endpoint, &errorMessage)) { qCWarning(QT_OPCUA_PLUGINS_UACPP) << errorMessage; emit stateAndOrErrorChanged(QOpcUaClient::Disconnected, QOpcUaClient::ClientError::InvalidUrl); return; } emit stateAndOrErrorChanged(QOpcUaClient::Connecting, QOpcUaClient::NoError); const auto identity = m_clientImpl->m_client->applicationIdentity(); const auto authInfo = m_clientImpl->m_client->authenticationInformation(); const auto pkiConfig = m_clientImpl->m_client->pkiConfiguration(); SessionConnectInfo sessionConnectInfo; sessionConnectInfo.sApplicationName = identity.applicationName().toUtf8().constData(); sessionConnectInfo.sApplicationUri = identity.applicationUri().toUtf8().constData(); sessionConnectInfo.sProductUri = identity.productUri().toUtf8().constData(); sessionConnectInfo.sSessionName = sessionConnectInfo.sApplicationUri; sessionConnectInfo.bAutomaticReconnect = OpcUa_False; sessionConnectInfo.applicationType = static_cast(identity.applicationType()); SessionSecurityInfo sessionSecurityInfo; if (pkiConfig.isPkiValid()) { auto result = sessionSecurityInfo.initializePkiProviderOpenSSL( pkiConfig.revocationListDirectory().toUtf8().constData(), pkiConfig.trustListDirectory().toUtf8().constData(), pkiConfig.issuerRevocationListDirectory().toUtf8().constData(), pkiConfig.issuerListDirectory().toUtf8().constData()); if (result.isNotGood()) { qCWarning(QT_OPCUA_PLUGINS_UACPP) << "Failed to set up PKI: " << QString::fromUtf8(result.toString().toUtf8()); emit stateAndOrErrorChanged(QOpcUaClient::Disconnected, QOpcUaClient::AccessDenied); return; } // set server certificate from endpoint sessionSecurityInfo.serverCertificate = UaByteString(endpoint.serverCertificate().length(), (OpcUa_Byte*)(endpoint.serverCertificate().constData())); // set SecurityPolicy sessionSecurityInfo.sSecurityPolicy = endpoint.securityPolicy().toUtf8().constData(); // set MessageSecurityMode sessionSecurityInfo.messageSecurityMode = static_cast(endpoint.securityMode()); } if (authInfo.authenticationType() == QOpcUaUserTokenPolicy::TokenType::Anonymous) { // nothing to do } else if (authInfo.authenticationType() == QOpcUaUserTokenPolicy::TokenType::Username) { const auto credentials = authInfo.authenticationData().value>(); UaString username(credentials.first.toUtf8().constData()); UaString password(credentials.second.toUtf8().constData()); sessionSecurityInfo.setUserPasswordUserIdentity(username, password); if (m_disableEncryptedPasswordCheck) sessionSecurityInfo.disableEncryptedPasswordCheck = OpcUa_True; } else if (authInfo.authenticationType() == QOpcUaUserTokenPolicy::TokenType::Certificate) { // try to load the client certificate const UaString certificateFilePath(pkiConfig.clientCertificateFile().toUtf8()); const UaString privateKeyFilePath(pkiConfig.privateKeyFile().toUtf8()); UaStatus result; QFile certFile(certificateFilePath.toUtf8()); if (certFile.open(QIODevice::ReadOnly)) { const auto data = certFile.read(1000); certFile.close(); if (data.contains("--BEGIN CERTIFICATE")) { qCWarning(QT_OPCUA_PLUGINS_UACPP) << "The UACPP backend supports only DER encoded certificates."; emit stateAndOrErrorChanged(QOpcUaClient::Disconnected, QOpcUaClient::AccessDenied); return; } } else { qCWarning(QT_OPCUA_PLUGINS_UACPP) << "Failed to load certificate: " << pkiConfig.clientCertificateFile(); result = OpcUa_BadNotFound; } QFile keyFile(privateKeyFilePath.toUtf8()); if (keyFile.open(QIODevice::ReadOnly)) { keyFile.close(); } else { qCWarning(QT_OPCUA_PLUGINS_UACPP) << "Failed to load private key: " << pkiConfig.privateKeyFile(); result = OpcUa_BadNotFound; } if (result.isGood()) { result = sessionSecurityInfo.loadClientCertificateOpenSSL(certificateFilePath, privateKeyFilePath); if (!result.isGood()) { qCWarning(QT_OPCUA_PLUGINS_UACPP) << "sessionSecurityInfo.loadClientCertificateOpenSSL failed"; QString password; do { // This signal is connected using Qt::BlockingQueuedConnection. It will place a metacall to a different thread and waits // until this metacall is fully handled before returning. emit QOpcUaBackend::passwordForPrivateKeyRequired(pkiConfig.privateKeyFile(), &password, !password.isEmpty()); if (password.isEmpty()) break; result = sessionSecurityInfo.loadClientCertificateOpenSSL(certificateFilePath, privateKeyFilePath, UaString(password.toUtf8())); if (result.isGood()) break; qCWarning(QT_OPCUA_PLUGINS_UACPP) << "sessionSecurityInfo.loadClientCertificateOpenSSL failed"; } while (true); } } if (result.isNotGood()) { emit stateAndOrErrorChanged(QOpcUaClient::Disconnected, QOpcUaClient::AccessDenied); qCWarning(QT_OPCUA_PLUGINS_UACPP) << "Failed to connect using certificate authentication: " << QString::fromUtf8(result.toString().toUtf8()); return; } } else { emit stateAndOrErrorChanged(QOpcUaClient::Disconnected, QOpcUaClient::UnsupportedAuthenticationInformation); qCWarning(QT_OPCUA_PLUGINS_UACPP) << "Failed to connect: Selected authentication type" << authInfo.authenticationType() << "is not supported."; return; } const UaString uaUrl(endpoint.endpointUrl().toUtf8().constData()); auto result = m_nativeSession->connect(uaUrl, sessionConnectInfo, sessionSecurityInfo, this); if (result.isNotGood()) { emit stateAndOrErrorChanged(QOpcUaClient::Disconnected, QOpcUaClient::AccessDenied); qCWarning(QT_OPCUA_PLUGINS_UACPP) << "Failed to connect:" << QString::fromUtf8(result.toString().toUtf8()); if (result.code() == OpcUa_BadEncodingLimitsExceeded && !endpoint.securityPolicy().endsWith("#None")) qCWarning(QT_OPCUA_PLUGINS_UACPP) << "Reason may be not using a DER encoded client certificate"; return; } } void UACppAsyncBackend::disconnectFromEndpoint() { cleanupSubscriptions(); UaStatus result; ServiceSettings serviceSettings; // Default settings const OpcUa_Boolean deleteSubscriptions{OpcUa_True}; result = m_nativeSession->disconnect(serviceSettings, deleteSubscriptions); QOpcUaClient::ClientError err = QOpcUaClient::NoError; if (result.isNotGood()) { qCWarning(QT_OPCUA_PLUGINS_UACPP) << "Failed to disconnect."; err = QOpcUaClient::UnknownError; } emit stateAndOrErrorChanged(QOpcUaClient::Disconnected, err); } void UACppAsyncBackend::requestEndpoints(const QUrl &url) { UaDiscovery discovery; ServiceSettings ServiceSettings; ClientSecurityInfo clientSecurityInfo; UaEndpointDescriptions endpoints; QVector ret; UaStatus res = discovery.getEndpoints(ServiceSettings, UaString(url.toString(QUrl::RemoveUserInfo).toUtf8().data()), clientSecurityInfo, endpoints); if (res.isGood() && endpoints.length()) { for (size_t i = 0; i < endpoints.length() ; ++i) { QOpcUaEndpointDescription temp; temp.setEndpointUrl(QString::fromUtf8(UaString(endpoints[i].EndpointUrl).toUtf8())); temp.serverRef().setApplicationUri(QString::fromUtf8(UaString(endpoints[i].Server.ApplicationUri).toUtf8())); temp.serverRef().setProductUri(QString::fromUtf8(UaString(endpoints[i].Server.ProductUri).toUtf8())); temp.serverRef().setApplicationName(QOpcUaLocalizedText(QString::fromUtf8(UaString(endpoints[i].Server.ApplicationName.Locale).toUtf8()), QString::fromUtf8(UaString(endpoints[i].Server.ApplicationName.Text).toUtf8()))); temp.serverRef().setApplicationType(static_cast(endpoints[i].Server.ApplicationType)); temp.serverRef().setGatewayServerUri(QString::fromUtf8(UaString(endpoints[i].Server.GatewayServerUri).toUtf8())); temp.serverRef().setDiscoveryProfileUri(QString::fromUtf8(UaString(endpoints[i].Server.DiscoveryProfileUri).toUtf8())); for (int j = 0; j < endpoints[i].Server.NoOfDiscoveryUrls; ++j) { QString url = QString::fromUtf8(UaString(endpoints[i].Server.DiscoveryUrls[j]).toUtf8()); temp.serverRef().discoveryUrlsRef().append(url); } temp.setServerCertificate(QByteArray(reinterpret_cast(endpoints[i].ServerCertificate.Data), endpoints[i].ServerCertificate.Length)); temp.setSecurityMode(static_cast(endpoints[i].SecurityMode)); temp.setSecurityPolicy(QString::fromUtf8(UaString(endpoints[i].SecurityPolicyUri).toUtf8())); for (int j = 0; j < endpoints[i].NoOfUserIdentityTokens; ++j) { QOpcUaUserTokenPolicy policy; policy.setPolicyId(QString::fromUtf8(UaString(endpoints[i].UserIdentityTokens[j].PolicyId).toUtf8())); policy.setTokenType(static_cast(endpoints[i].UserIdentityTokens[j].TokenType)); policy.setIssuedTokenType(QString::fromUtf8(UaString(endpoints[i].UserIdentityTokens[j].IssuedTokenType).toUtf8())); policy.setIssuerEndpointUrl(QString::fromUtf8(UaString(endpoints[i].UserIdentityTokens[j].IssuerEndpointUrl).toUtf8())); policy.setSecurityPolicy(QString::fromUtf8(UaString(endpoints[i].UserIdentityTokens[j].SecurityPolicyUri).toUtf8())); temp.userIdentityTokensRef().append(policy); } temp.setTransportProfileUri(QString::fromUtf8(UaString(endpoints[i].TransportProfileUri).toUtf8())); temp.setSecurityLevel(endpoints[i].SecurityLevel); ret.append(temp); } } else { if (res.isGood()) qWarning() << "Server returned an empty endpoint list"; else qWarning() << "Failed to retrive endpoints from " << url.toString(QUrl::RemoveUserInfo).toUtf8().constData() << "with status" << res.toString().toUtf8(); } emit endpointsRequestFinished(ret, static_cast(res.code()), url); } void UACppAsyncBackend::readAttributes(quint64 handle, const UaNodeId &id, QOpcUa::NodeAttributes attr, QString indexRange) { UaStatus result; ServiceSettings settings; UaReadValueIds nodeToRead; UaDataValues values; UaDiagnosticInfos diagnosticInfos; QVector vec; int attributeSize = 0; qt_forEachAttribute(attr, [&](QOpcUa::NodeAttribute attribute){ attributeSize++; nodeToRead.resize(attributeSize); id.copyTo(&nodeToRead[attributeSize - 1].NodeId); nodeToRead[attributeSize - 1].AttributeId = QUACppValueConverter::toUaAttributeId(attribute); if (indexRange.size()) { UaString ir(indexRange.toUtf8().constData()); ir.copyTo(&nodeToRead[attributeSize - 1].IndexRange); } QOpcUaReadResult temp; temp.setAttribute(attribute); vec.push_back(temp); }); result = m_nativeSession->read(settings, 0, OpcUa_TimestampsToReturn_Both, nodeToRead, values, diagnosticInfos); if (result.isBad()) { qCWarning(QT_OPCUA_PLUGINS_UACPP) << "Reading attributes failed:" << result.toString().toUtf8(); } else { for (int i = 0; i < vec.size(); ++i) { vec[i].setStatusCode(static_cast(values[i].StatusCode)); vec[i].setValue(QUACppValueConverter::toQVariant(values[i].Value)); vec[i].setServerTimestamp(QUACppValueConverter::toQDateTime(&values[i].ServerTimestamp)); vec[i].setSourceTimestamp(QUACppValueConverter::toQDateTime(&values[i].SourceTimestamp)); } } emit attributesRead(handle, vec, static_cast(result.statusCode())); } void UACppAsyncBackend::writeAttribute(quint64 handle, const UaNodeId &id, QOpcUa::NodeAttribute attrId, QVariant value, QOpcUa::Types type, QString indexRange) { if (type == QOpcUa::Types::Undefined && attrId != QOpcUa::NodeAttribute::Value) type = attributeIdToTypeId(attrId); UaStatus result; ServiceSettings settings; UaWriteValues nodesToWrite; UaStatusCodeArray writeResults; UaDiagnosticInfos diagnosticInfos; nodesToWrite.create(1); id.copyTo(&nodesToWrite[0].NodeId); nodesToWrite[0].AttributeId = QUACppValueConverter::toUaAttributeId(attrId); nodesToWrite[0].Value.Value = QUACppValueConverter::toUACppVariant(value, type); if (indexRange.size()) { UaString ir(indexRange.toUtf8()); ir.copyTo(&nodesToWrite[0].IndexRange); } result = m_nativeSession->write(settings, nodesToWrite, writeResults, diagnosticInfos); if (result.isBad()) qCWarning(QT_OPCUA_PLUGINS_UACPP) << "Writing attribute failed:" << result.toString().toUtf8(); emit attributeWritten(handle, attrId, result.isGood() ? value : QVariant(), writeResults.length() ? static_cast(writeResults[0]) : static_cast(result.statusCode())); } void UACppAsyncBackend::writeAttributes(quint64 handle, const UaNodeId &id, QOpcUaNode::AttributeMap toWrite, QOpcUa::Types valueAttributeType) { if (toWrite.size() == 0) { qCWarning(QT_OPCUA_PLUGINS_UACPP, "No values to be written"); emit attributeWritten(handle, QOpcUa::NodeAttribute::None, QVariant(), QOpcUa::UaStatusCode::BadNothingToDo); return; } UaStatus result; ServiceSettings settings; UaWriteValues nodesToWrite; UaStatusCodeArray writeResults; UaDiagnosticInfos diagnosticInfos; nodesToWrite.create(toWrite.size()); quint32 index = 0; for (auto it = toWrite.constBegin(); it != toWrite.constEnd(); ++it, ++index) { id.copyTo(&nodesToWrite[index].NodeId); QOpcUa::Types type = attributeIdToTypeId(it.key()); if (type == QOpcUa::Types::Undefined) type = valueAttributeType; nodesToWrite[index].AttributeId = QUACppValueConverter::toUaAttributeId(it.key()); nodesToWrite[index].Value.Value = QUACppValueConverter::toUACppVariant(it.value(), type); } result = m_nativeSession->write(settings, nodesToWrite, writeResults, diagnosticInfos); if (result.isBad()) qCWarning(QT_OPCUA_PLUGINS_UACPP) << "Writing attribute failed:" << result.toString().toUtf8(); index = 0; for (auto it = toWrite.constBegin(); it != toWrite.constEnd(); ++it, ++index) { QOpcUa::UaStatusCode status = index < writeResults.length() ? static_cast(writeResults[index]) : static_cast(result.statusCode()); emit attributeWritten(handle, it.key(), it.value(), status); } } void UACppAsyncBackend::enableMonitoring(quint64 handle, const UaNodeId &id, QOpcUa::NodeAttributes attr, const QOpcUaMonitoringParameters &settings) { QUACppSubscription *usedSubscription = nullptr; // Create a new subscription if necessary if (settings.subscriptionId()) { auto sub = m_subscriptions.find(settings.subscriptionId()); if (sub == m_subscriptions.end()) { qCWarning(QT_OPCUA_PLUGINS_UACPP, "There is no subscription with id %u", settings.subscriptionId()); qt_forEachAttribute(attr, [&](QOpcUa::NodeAttribute attribute){ QOpcUaMonitoringParameters s; s.setStatusCode(QOpcUa::UaStatusCode::BadSubscriptionIdInvalid); emit monitoringEnableDisable(handle, attribute, true, s); }); return; } usedSubscription = sub.value(); // Ignore interval != subscription.interval } else { usedSubscription = getSubscription(settings); } if (!usedSubscription) { qCWarning(QT_OPCUA_PLUGINS_UACPP, "Could not create subscription with interval %f", settings.publishingInterval()); qt_forEachAttribute(attr, [&](QOpcUa::NodeAttribute attribute){ QOpcUaMonitoringParameters s; s.setStatusCode(QOpcUa::UaStatusCode::BadSubscriptionIdInvalid); emit monitoringEnableDisable(handle, attribute, true, s); }); return; } qt_forEachAttribute(attr, [&](QOpcUa::NodeAttribute attribute){ if (getSubscriptionForItem(handle, attribute)) { qCWarning(QT_OPCUA_PLUGINS_UACPP) << "Monitored item for" << attribute << "has already been created"; QOpcUaMonitoringParameters s; s.setStatusCode(QOpcUa::UaStatusCode::BadEntryExists); emit monitoringEnableDisable(handle, attribute, true, s); } else { bool success = usedSubscription->addAttributeMonitoredItem(handle, attribute, id, settings); if (success) m_attributeMapping[handle][attribute] = usedSubscription; } }); if (usedSubscription->monitoredItemsCount() == 0) removeSubscription(usedSubscription->subscriptionId()); // No items were added } void UACppAsyncBackend::modifyMonitoring(quint64 handle, QOpcUa::NodeAttribute attr, QOpcUaMonitoringParameters::Parameter item, QVariant value) { QUACppSubscription *subscription = getSubscriptionForItem(handle, attr); if (!subscription) { qCWarning(QT_OPCUA_PLUGINS_UACPP) << "Could not modify" << item << ", the monitored item does not exist"; QOpcUaMonitoringParameters p; p.setStatusCode(QOpcUa::UaStatusCode::BadMonitoredItemIdInvalid); emit monitoringStatusChanged(handle, attr, item, p); return; } subscription->modifyMonitoring(handle, attr, item, value); } void UACppAsyncBackend::disableMonitoring(quint64 handle, QOpcUa::NodeAttributes attr) { qt_forEachAttribute(attr, [&](QOpcUa::NodeAttribute attribute){ QUACppSubscription *sub = getSubscriptionForItem(handle, attribute); if (sub) { sub->removeAttributeMonitoredItem(handle, attribute); m_attributeMapping[handle].remove(attribute); if (sub->monitoredItemsCount() == 0) removeSubscription(sub->subscriptionId()); } }); } void UACppAsyncBackend::callMethod(quint64 handle, const UaNodeId &objectId, const UaNodeId &methodId, QVector args) { ServiceSettings settings; CallIn in; in.objectId = objectId; in.methodId = methodId; if (args.size()) { in.inputArguments.resize(args.size()); for (int i = 0; i < args.size(); ++i) in.inputArguments[i] = QUACppValueConverter::toUACppVariant(args[i].first, args[i].second); } CallOut out; UaStatus status = m_nativeSession->call(settings, in, out); if (status.isBad()) qCWarning(QT_OPCUA_PLUGINS_UACPP) << "Calling method failed"; if (out.callResult.isBad()) qCWarning(QT_OPCUA_PLUGINS_UACPP) << "Method call returned a failure"; QVariant result; if (out.outputArguments.length() > 1) { QVariantList resultList; for (quint32 i = 0; i < out.outputArguments.length(); ++i) resultList.append(QUACppValueConverter::toQVariant(out.outputArguments[i])); result = resultList; } else if (out.outputArguments.length() == 1) { result = QUACppValueConverter::toQVariant(out.outputArguments[0]); } emit methodCallFinished(handle, UACppUtils::nodeIdToQString(methodId), result, static_cast(status.statusCode())); } void UACppAsyncBackend::resolveBrowsePath(quint64 handle, const UaNodeId &startNode, const QVector &path) { ServiceSettings settings; UaDiagnosticInfos diagnosticInfos; UaBrowsePaths paths; UaBrowsePathResults result; UaRelativePathElements pathElements; paths.create(1); startNode.copyTo(&paths[0].StartingNode); pathElements.create(path.size()); for (int i = 0; i < path.size(); ++i) { pathElements[i].IncludeSubtypes = path[i].includeSubtypes(); pathElements[i].IsInverse = path[i].isInverse(); UaNodeId(UACppUtils::nodeIdFromQString(path[i].referenceTypeId())).copyTo(&pathElements[i].ReferenceTypeId); UaQualifiedName(UaString(path[i].targetName().name().toUtf8().constData()), path[i].targetName().namespaceIndex()).copyTo(&pathElements[i].TargetName); } paths[0].RelativePath.Elements = pathElements.detach(); paths[0].RelativePath.NoOfElements = path.size(); UaStatusCode serviceResult = m_nativeSession->translateBrowsePathsToNodeIds(settings, paths, result, diagnosticInfos); QOpcUa::UaStatusCode status = static_cast(serviceResult.code()); QVector ret; if (status == QOpcUa::UaStatusCode::Good && result.length()) { status = static_cast(result[0].StatusCode); for (int i = 0; i < result[0].NoOfTargets; ++i) { QOpcUaBrowsePathTarget temp; temp.setRemainingPathIndex(result[0].Targets[i].RemainingPathIndex); temp.targetIdRef().setNamespaceUri(QString::fromUtf8(UaString(result[0].Targets[i].TargetId.NamespaceUri).toUtf8())); temp.targetIdRef().setServerIndex(result[0].Targets[i].TargetId.ServerIndex); temp.targetIdRef().setNodeId(UACppUtils::nodeIdToQString(result[0].Targets[i].TargetId.NodeId)); ret.append(temp); } } emit resolveBrowsePathFinished(handle, ret, path, status); } QUACppSubscription *UACppAsyncBackend::getSubscription(const QOpcUaMonitoringParameters &settings) { if (settings.subscriptionType() == QOpcUaMonitoringParameters::SubscriptionType::Shared) { // Requesting multiple subscriptions with publishing interval < minimum publishing interval breaks subscription sharing double interval = revisePublishingInterval(settings.publishingInterval(), m_minPublishingInterval); for (auto entry : qAsConst(m_subscriptions)) { if (qFuzzyCompare(entry->interval(), interval) && entry->shared() == QOpcUaMonitoringParameters::SubscriptionType::Shared) return entry; } } QUACppSubscription *sub = new QUACppSubscription(this, settings); quint32 id = sub->createOnServer(); if (!id) { delete sub; return nullptr; } if (sub->interval() > settings.samplingInterval()) // The publishing interval has been revised by the server. m_minPublishingInterval = sub->interval(); m_subscriptions[id] = sub; return sub; } QUACppSubscription *UACppAsyncBackend::getSubscriptionForItem(quint64 handle, QOpcUa::NodeAttribute attr) { auto entriesForHandle = m_attributeMapping.find(handle); if (entriesForHandle == m_attributeMapping.end()) return nullptr; auto subscription = entriesForHandle->find(attr); if (subscription == entriesForHandle->end()) return nullptr; return subscription.value(); } void UACppAsyncBackend::cleanupSubscriptions() { qDeleteAll(m_subscriptions); m_subscriptions.clear(); m_attributeMapping.clear(); m_minPublishingInterval = 0; } bool UACppAsyncBackend::removeSubscription(quint32 subscriptionId) { auto sub = m_subscriptions.find(subscriptionId); if (sub != m_subscriptions.end()) { sub.value()->removeOnServer(); delete sub.value(); m_subscriptions.remove(subscriptionId); return true; } return false; } void UACppAsyncBackend::findServers(const QUrl &url, const QStringList &localeIds, const QStringList &serverUris) { UaStringArray uaServerUris = QUACppValueConverter::toUaStringArray(serverUris); UaStringArray uaLocaleIds = QUACppValueConverter::toUaStringArray(localeIds); UaDiscovery discovery; ServiceSettings serviceSettings; ClientSecurityInfo clientSecurityInfo; UaApplicationDescriptions applicationDescriptions; UaStatus result = discovery.findServers(serviceSettings, UaString(url.toString(QUrl::RemoveUserInfo).toUtf8().constData()), clientSecurityInfo, uaLocaleIds, uaServerUris, applicationDescriptions); QVector ret; for (OpcUa_UInt32 i = 0; i < applicationDescriptions.length(); ++i) { QOpcUaApplicationDescription temp; const UaApplicationDescription desc = applicationDescriptions[i]; temp.setApplicationUri(QString::fromUtf8(desc.getApplicationUri().toUtf8())); temp.setProductUri(QString::fromUtf8(desc.getProductUri().toUtf8())); UaLocalizedText uaApplicationName(desc.getApplicationName()); temp.setApplicationName(QUACppValueConverter::toQOpcUaLocalizedText(&uaApplicationName)); temp.setApplicationType(static_cast(desc.getApplicationType())); temp.setGatewayServerUri(QString::fromUtf8(desc.getGatewayServerUri().toUtf8())); temp.setDiscoveryProfileUri(QString::fromUtf8(desc.getDiscoveryProfileUri().toUtf8())); UaStringArray discoveryUrls; desc.getDiscoveryUrls(discoveryUrls); for (OpcUa_UInt32 i = 0; i < discoveryUrls.length(); ++i) temp.discoveryUrlsRef().append(QString::fromUtf8(UaString(discoveryUrls[i]).toUtf8())); ret.append(temp); } if (result.isNotGood()) { qCDebug(QT_OPCUA_PLUGINS_UACPP) << "Failed to get servers:" << static_cast(result.statusCode()); } emit findServersFinished(ret, static_cast(result.statusCode()), url); } void UACppAsyncBackend::readNodeAttributes(const QVector &nodesToRead) { if (nodesToRead.size() == 0) { emit readNodeAttributesFinished(QVector(), QOpcUa::UaStatusCode::BadNothingToDo); return; } UaReadValueIds nodesToReadNativeType; nodesToReadNativeType.resize(nodesToRead.size()); for (int i = 0; i < nodesToRead.size(); ++i) { UACppUtils::nodeIdFromQString(nodesToRead[i].nodeId()).copyTo(&nodesToReadNativeType[i].NodeId); nodesToReadNativeType[i].AttributeId = QUACppValueConverter::toUaAttributeId(nodesToRead[i].attribute()); if (!nodesToRead[i].indexRange().isEmpty()) { UaString ir(nodesToRead[i].indexRange().toUtf8()); ir.copyTo(&nodesToReadNativeType[i].IndexRange); } } UaDataValues values; UaDiagnosticInfos diagnosticInfos; ServiceSettings serviceSettings; UaStatus result = m_nativeSession->read(serviceSettings, 0.0 /* maxAge */, OpcUa_TimestampsToReturn_Both, nodesToReadNativeType, values, diagnosticInfos); QOpcUa::UaStatusCode status = static_cast(result.code()); if (result.isBad()) { qCWarning(QT_OPCUA_PLUGINS_UACPP) << "Batch read failed:" << result.toString(); emit readNodeAttributesFinished(QVector(), status); } else { QVector ret; for (int i = 0; i < nodesToRead.size(); ++i) { QOpcUaReadResult item; item.setAttribute(nodesToRead.at(i).attribute()); item.setNodeId(nodesToRead.at(i).nodeId()); item.setIndexRange(nodesToRead.at(i).indexRange()); if (static_cast(i) < values.length()) { UaDataValue dataValue(values[i]); item.setValue(QUACppValueConverter::toQVariant(*dataValue.value())); item.setStatusCode(static_cast(dataValue.statusCode())); if (dataValue.isServerTimestampSet()) { auto dt = dataValue.serverTimestamp(); item.setServerTimestamp(QUACppValueConverter::toQDateTime(&dt)); } if (dataValue.isSourceTimestampSet()) { auto dt = dataValue.sourceTimestamp(); item.setSourceTimestamp(QUACppValueConverter::toQDateTime(&dt)); } } else { item.setStatusCode(status); } ret.push_back(item); } emit readNodeAttributesFinished(ret, status); } } void UACppAsyncBackend::writeNodeAttributes(const QVector &nodesToWrite) { if (nodesToWrite.isEmpty()) { emit writeNodeAttributesFinished(QVector(), QOpcUa::UaStatusCode::BadNothingToDo); return; } UaWriteValues nodesToWriteNativeType; nodesToWriteNativeType.create(nodesToWrite.size()); for (int i = 0; i < nodesToWrite.size(); ++i) { const auto ¤tItem = nodesToWrite[i]; UaDataValue dataValue; auto value = QUACppValueConverter::toUACppVariant(currentItem.value(), currentItem.type()); dataValue.setValue(value, true); // true == detach value if (currentItem.hasStatusCode()) dataValue.setStatusCode(currentItem.statusCode()); if (currentItem.serverTimestamp().isValid()) dataValue.setServerTimestamp(QUACppValueConverter::toUACppDateTime(currentItem.serverTimestamp())); if (currentItem.sourceTimestamp().isValid()) dataValue.setSourceTimestamp(QUACppValueConverter::toUACppDateTime(currentItem.sourceTimestamp())); dataValue.copyTo(&nodesToWriteNativeType[i].Value); UACppUtils::nodeIdFromQString(currentItem.nodeId()).copyTo(&nodesToWriteNativeType[i].NodeId); nodesToWriteNativeType[i].AttributeId = QUACppValueConverter::toUaAttributeId(currentItem.attribute()); if (!currentItem.indexRange().isEmpty()) { UaString ir(currentItem.indexRange().toUtf8()); ir.copyTo(&nodesToWriteNativeType[i].IndexRange); } } UaStatusCodeArray results; UaDiagnosticInfos diagnosticInfos; ServiceSettings serviceSettings; UaStatus result = m_nativeSession->write(serviceSettings, nodesToWriteNativeType, results, diagnosticInfos); const QOpcUa::UaStatusCode status = static_cast(result.code()); if (result.isBad()) { qCWarning(QT_OPCUA_PLUGINS_UACPP) << "Batch write failed:" << result.toString(); emit writeNodeAttributesFinished(QVector(), status); } else { QVector ret; for (int i = 0; i < nodesToWrite.size(); ++i) { QOpcUaWriteResult item; item.setAttribute(nodesToWrite.at(i).attribute()); item.setNodeId(nodesToWrite.at(i).nodeId()); item.setIndexRange(nodesToWrite.at(i).indexRange()); if (static_cast(i) < results.length()) item.setStatusCode(static_cast(results[i])); else item.setStatusCode(status); ret.push_back(item); } emit writeNodeAttributesFinished(ret, status); } } void UACppAsyncBackend::addNode(const QOpcUaAddNodeItem &nodeToAdd) { OpcUa_AddNodesItem* opcUaNodeToAdd = static_cast(OpcUa_Alloc(1 * sizeof(OpcUa_AddNodesItem))); OpcUa_AddNodesItem_Initialize(opcUaNodeToAdd); opcUaNodeToAdd->ParentNodeId = QUACppValueConverter::toUACppExpandedNodeId(nodeToAdd.parentNodeId()); UACppUtils::nodeIdFromQString(nodeToAdd.referenceTypeId()).detach(&opcUaNodeToAdd->ReferenceTypeId); opcUaNodeToAdd->RequestedNewNodeId = QUACppValueConverter::toUACppExpandedNodeId(nodeToAdd.requestedNewNodeId()); opcUaNodeToAdd->BrowseName = QUACppValueConverter::toUACppQualifiedName(nodeToAdd.browseName()); opcUaNodeToAdd->NodeClass = static_cast(nodeToAdd.nodeClass()); if (!assembleNodeAttributes(&opcUaNodeToAdd->NodeAttributes, nodeToAdd.nodeAttributes(), nodeToAdd.nodeClass())) { qCWarning(QT_OPCUA_PLUGINS_UACPP) << "Failed to assemble node attributes"; emit addNodeFinished(nodeToAdd.requestedNewNodeId(), QString(), QOpcUa::BadUnexpectedError); return; } if (!nodeToAdd.typeDefinition().nodeId().isEmpty()) opcUaNodeToAdd->TypeDefinition = QUACppValueConverter::toUACppExpandedNodeId(nodeToAdd.typeDefinition()); UaAddNodesItems itemsToAdd; itemsToAdd.attach((OpcUa_UInt32)1, opcUaNodeToAdd); UaAddNodesResults results; UaDiagnosticInfos diagnosticInfos; ServiceSettings serviceSettings; UaStatus result = m_nativeSession->addNodes(serviceSettings, itemsToAdd, results, diagnosticInfos); QOpcUa::UaStatusCode status = static_cast(result.code()); QString resultId; if (result.isGood() && results.length() > 0) { if (OpcUa_IsGood(results[0].StatusCode)) { resultId = UACppUtils::nodeIdToQString(results[0].AddedNodeId); } else { status = static_cast(results[0].StatusCode); qCDebug(QT_OPCUA_PLUGINS_UACPP) << "Failed to add node:" << status; } } else { qCDebug(QT_OPCUA_PLUGINS_UACPP) << "Failed to add node:" << status; } emit addNodeFinished(nodeToAdd.requestedNewNodeId(), resultId, status); } void UACppAsyncBackend::deleteNode(const QString &nodeId, bool deleteTargetReferences) { UaNodeId id = UACppUtils::nodeIdFromQString(nodeId); UaDeleteNodesItems nodesToDelete; nodesToDelete.attach((OpcUa_UInt32)1, static_cast(OpcUa_Alloc(1 * sizeof(OpcUa_DeleteNodesItem)))); OpcUa_DeleteNodesItem_Initialize(&nodesToDelete[0]); id.copyTo(&nodesToDelete[0].NodeId); nodesToDelete[0].DeleteTargetReferences = deleteTargetReferences; UaDiagnosticInfos diagnosticInfos; ServiceSettings serviceSettings; UaStatusCodeArray results; UaStatus res = m_nativeSession->deleteNodes(serviceSettings, nodesToDelete, results, diagnosticInfos); QOpcUa::UaStatusCode resultStatus = static_cast(res.statusCode()); if (resultStatus != QOpcUa::UaStatusCode::Good) { qCDebug(QT_OPCUA_PLUGINS_UACPP) << "Failed to delete node" << nodeId << "with status code" << resultStatus; } emit deleteNodeFinished(nodeId, resultStatus); } void UACppAsyncBackend::addReference(const QOpcUaAddReferenceItem &referenceToAdd) { UaAddReferencesItems referencesToAdd; referencesToAdd.attach((OpcUa_UInt32)1, static_cast(OpcUa_Alloc(1 * sizeof(OpcUa_AddReferencesItem)))); OpcUa_AddReferencesItem_Initialize(&referencesToAdd[0]); UACppUtils::nodeIdFromQString(referenceToAdd.sourceNodeId()).copyTo(&referencesToAdd[0].SourceNodeId); UACppUtils::nodeIdFromQString(referenceToAdd.referenceTypeId()).copyTo(&referencesToAdd[0].ReferenceTypeId); referencesToAdd[0].IsForward = referenceToAdd.isForwardReference(); UaString(referenceToAdd.targetServerUri().toUtf8().constData()).detach(&referencesToAdd[0].TargetServerUri); referencesToAdd[0].TargetNodeId = QUACppValueConverter::toUACppExpandedNodeId(referenceToAdd.targetNodeId()); referencesToAdd[0].TargetNodeClass = static_cast(referenceToAdd.targetNodeClass()); UaDiagnosticInfos diagnosticInfos; ServiceSettings serviceSettings; UaStatusCodeArray results; UaStatus res = m_nativeSession->addReferences(serviceSettings, referencesToAdd, results, diagnosticInfos); QOpcUa::UaStatusCode statusCode = static_cast(res.statusCode()); if (res.isBad()) qCDebug(QT_OPCUA_PLUGINS_UACPP) << "Failed to add reference from" << referenceToAdd.sourceNodeId() << "to" << referenceToAdd.targetNodeId().nodeId() << ":" << statusCode; emit addReferenceFinished(referenceToAdd.sourceNodeId(), referenceToAdd.referenceTypeId(), referenceToAdd.targetNodeId(), referenceToAdd.isForwardReference(), statusCode); } void UACppAsyncBackend::deleteReference(const QOpcUaDeleteReferenceItem &referenceToDelete) { UaDeleteReferencesItems referencesToDelete; referencesToDelete.attach((OpcUa_UInt32)1, static_cast(OpcUa_Alloc(1 * sizeof(OpcUa_DeleteReferencesItem)))); OpcUa_DeleteReferencesItem_Initialize(&referencesToDelete[0]); UACppUtils::nodeIdFromQString(referenceToDelete.sourceNodeId()).copyTo(&referencesToDelete[0].SourceNodeId); UACppUtils::nodeIdFromQString(referenceToDelete.referenceTypeId()).copyTo(&referencesToDelete[0].ReferenceTypeId); referencesToDelete[0].IsForward = referenceToDelete.isForwardReference(); referencesToDelete[0].TargetNodeId = QUACppValueConverter::toUACppExpandedNodeId(referenceToDelete.targetNodeId()); referencesToDelete[0].DeleteBidirectional = referenceToDelete.deleteBidirectional(); UaDiagnosticInfos diagnosticInfos; ServiceSettings serviceSettings; UaStatusCodeArray results; UaStatus res = m_nativeSession->deleteReferences(serviceSettings, referencesToDelete, results, diagnosticInfos); QOpcUa::UaStatusCode statusCode = static_cast(res.statusCode()); if (res.isBad()) qCDebug(QT_OPCUA_PLUGINS_UACPP) << "Failed to delete reference from" << referenceToDelete.sourceNodeId() << "to" << referenceToDelete.targetNodeId().nodeId() << ":" << statusCode; emit deleteReferenceFinished(referenceToDelete.sourceNodeId(), referenceToDelete.referenceTypeId(), referenceToDelete.targetNodeId(), referenceToDelete.isForwardReference(), statusCode); } QOpcUaErrorState::ConnectionStep UACppAsyncBackend::connectionStepFromUaServiceType( UaClientSdk::UaClient::ConnectServiceType type) const { switch (type) { case UaClientSdk::UaClient::ConnectServiceType::CreateSession: return QOpcUaErrorState::ConnectionStep::CreateSession; case UaClientSdk::UaClient::ConnectServiceType::ActivateSession: return QOpcUaErrorState::ConnectionStep::ActivateSession; case UaClientSdk::UaClient::ConnectServiceType::OpenSecureChannel: return QOpcUaErrorState::ConnectionStep::OpenSecureChannel; case UaClientSdk::UaClient::ConnectServiceType::CertificateValidation: return QOpcUaErrorState::ConnectionStep::CertificateValidation; default: return QOpcUaErrorState::ConnectionStep::Unknown; } } static void copyArrayDimensions(OpcUa_Int32 *noOfDimensions, OpcUa_UInt32 **arrayDimensions, const QVector &dimensions) { *noOfDimensions = static_cast(dimensions.size()); *arrayDimensions = static_cast(OpcUa_Alloc(*noOfDimensions * sizeof(OpcUa_UInt32))); std::copy(dimensions.constBegin(), dimensions.constEnd(), *arrayDimensions); } #define CREATE_NODE_ATTRIBUTE(TYPE) \ OpcUa_ ##TYPE ## Attributes* uaNodeAttributes = nullptr; \ OpcUa_EncodeableObject_CreateExtension(&OpcUa_ ##TYPE ## Attributes_EncodeableType, \ uaExtensionObject, (OpcUa_Void**)&uaNodeAttributes); \ if (!uaNodeAttributes) \ return false; \ \ OpcUa_ ##TYPE ## Attributes_Initialize(uaNodeAttributes); \ \ if (nodeAttributes.hasDisplayName()) { \ uaNodeAttributes->SpecifiedAttributes |= OpcUa_NodeAttributesMask_DisplayName; \ uaNodeAttributes->DisplayName = QUACppValueConverter::toUACppLocalizedText(nodeAttributes.displayName()); \ } \ if (nodeAttributes.hasDescription()) { \ uaNodeAttributes->SpecifiedAttributes |= OpcUa_NodeAttributesMask_Description; \ uaNodeAttributes->Description = QUACppValueConverter::toUACppLocalizedText(nodeAttributes.description()); \ } \ if (nodeAttributes.hasWriteMask()) { \ uaNodeAttributes->SpecifiedAttributes |= OpcUa_NodeAttributesMask_WriteMask; \ uaNodeAttributes->WriteMask = nodeAttributes.writeMask(); \ } \ if (nodeAttributes.hasUserWriteMask()) { \ uaNodeAttributes->SpecifiedAttributes |= OpcUa_NodeAttributesMask_UserWriteMask; \ uaNodeAttributes->UserWriteMask = nodeAttributes.userWriteMask(); \ } \ bool UACppAsyncBackend::assembleNodeAttributes(OpcUa_ExtensionObject *uaExtensionObject, const QOpcUaNodeCreationAttributes &nodeAttributes, QOpcUa::NodeClass nodeClass) { switch (nodeClass) { case QOpcUa::NodeClass::Object: { CREATE_NODE_ATTRIBUTE(Object); if (nodeAttributes.hasEventNotifier()) { uaNodeAttributes->SpecifiedAttributes |= OpcUa_NodeAttributesMask_EventNotifier; uaNodeAttributes->EventNotifier = nodeAttributes.eventNotifier(); } break; } case QOpcUa::NodeClass::Variable: { CREATE_NODE_ATTRIBUTE(Variable); if (nodeAttributes.hasValue()) { uaNodeAttributes->SpecifiedAttributes |= OpcUa_NodeAttributesMask_Value; uaNodeAttributes->Value = QUACppValueConverter::toUACppVariant(nodeAttributes.value(), nodeAttributes.valueType()); } if (nodeAttributes.hasDataTypeId()) { uaNodeAttributes->SpecifiedAttributes |= OpcUa_NodeAttributesMask_DataType; UACppUtils::nodeIdFromQString(nodeAttributes.dataTypeId()).detach(&uaNodeAttributes->DataType); } if (nodeAttributes.hasValueRank()) { uaNodeAttributes->SpecifiedAttributes |= OpcUa_NodeAttributesMask_ValueRank; uaNodeAttributes->ValueRank = nodeAttributes.valueRank(); } if (nodeAttributes.hasArrayDimensions()) { uaNodeAttributes->SpecifiedAttributes |= OpcUa_NodeAttributesMask_ArrayDimensions; copyArrayDimensions(&uaNodeAttributes->NoOfArrayDimensions,&uaNodeAttributes->ArrayDimensions, nodeAttributes.arrayDimensions()); } if (nodeAttributes.hasAccessLevel()) { uaNodeAttributes->SpecifiedAttributes |= OpcUa_NodeAttributesMask_AccessLevel; uaNodeAttributes->AccessLevel = nodeAttributes.accessLevel(); } if (nodeAttributes.hasUserAccessLevel()) { uaNodeAttributes->SpecifiedAttributes |= OpcUa_NodeAttributesMask_UserAccessLevel; uaNodeAttributes->UserAccessLevel = nodeAttributes.userAccessLevel(); } if (nodeAttributes.hasMinimumSamplingInterval()) { uaNodeAttributes->SpecifiedAttributes |= OpcUa_NodeAttributesMask_MinimumSamplingInterval; uaNodeAttributes->MinimumSamplingInterval = nodeAttributes.minimumSamplingInterval(); } if (nodeAttributes.hasHistorizing()) { uaNodeAttributes->SpecifiedAttributes |= OpcUa_NodeAttributesMask_Historizing; uaNodeAttributes->Historizing = nodeAttributes.historizing(); } break; } case QOpcUa::NodeClass::Method: { CREATE_NODE_ATTRIBUTE(Method); if (nodeAttributes.hasExecutable()) { uaNodeAttributes->SpecifiedAttributes |= OpcUa_NodeAttributesMask_Executable; uaNodeAttributes->Executable = nodeAttributes.executable(); } if (nodeAttributes.hasUserExecutable()) { uaNodeAttributes->SpecifiedAttributes |= OpcUa_NodeAttributesMask_UserExecutable; uaNodeAttributes->UserExecutable = nodeAttributes.userExecutable(); } break; } case QOpcUa::NodeClass::ObjectType: { CREATE_NODE_ATTRIBUTE(ObjectType); if (nodeAttributes.hasIsAbstract()) { uaNodeAttributes->SpecifiedAttributes |= OpcUa_NodeAttributesMask_IsAbstract; uaNodeAttributes->IsAbstract = nodeAttributes.isAbstract(); } break; } case QOpcUa::NodeClass::VariableType: { CREATE_NODE_ATTRIBUTE(VariableType); if (nodeAttributes.hasValue()) { uaNodeAttributes->SpecifiedAttributes |= OpcUa_NodeAttributesMask_Value; uaNodeAttributes->Value = QUACppValueConverter::toUACppVariant(nodeAttributes.value(), nodeAttributes.valueType()); } if (nodeAttributes.hasDataTypeId()) { uaNodeAttributes->SpecifiedAttributes |= OpcUa_NodeAttributesMask_DataType; UACppUtils::nodeIdFromQString(nodeAttributes.dataTypeId()).detach(&uaNodeAttributes->DataType); } if (nodeAttributes.hasValueRank()) { uaNodeAttributes->SpecifiedAttributes |= OpcUa_NodeAttributesMask_ValueRank; uaNodeAttributes->ValueRank = nodeAttributes.valueRank(); } if (nodeAttributes.hasArrayDimensions()) { uaNodeAttributes->SpecifiedAttributes |= OpcUa_NodeAttributesMask_ArrayDimensions; copyArrayDimensions(&uaNodeAttributes->NoOfArrayDimensions, &uaNodeAttributes->ArrayDimensions, nodeAttributes.arrayDimensions()); } if (nodeAttributes.hasIsAbstract()) { uaNodeAttributes->SpecifiedAttributes |= OpcUa_NodeAttributesMask_IsAbstract; uaNodeAttributes->IsAbstract = nodeAttributes.isAbstract(); } break; } case QOpcUa::NodeClass::ReferenceType: { CREATE_NODE_ATTRIBUTE(ReferenceType); if (nodeAttributes.hasIsAbstract()) { uaNodeAttributes->SpecifiedAttributes |= OpcUa_NodeAttributesMask_IsAbstract; uaNodeAttributes->IsAbstract = nodeAttributes.isAbstract(); } if (nodeAttributes.hasSymmetric()) { uaNodeAttributes->SpecifiedAttributes |= OpcUa_NodeAttributesMask_Symmetric; uaNodeAttributes->Symmetric = nodeAttributes.symmetric(); } if (nodeAttributes.hasInverseName()) { uaNodeAttributes->SpecifiedAttributes |= OpcUa_NodeAttributesMask_InverseName; uaNodeAttributes->InverseName = QUACppValueConverter::toUACppLocalizedText(nodeAttributes.inverseName()); } break; } case QOpcUa::NodeClass::DataType: { CREATE_NODE_ATTRIBUTE(DataType); if (nodeAttributes.hasIsAbstract()) { uaNodeAttributes->SpecifiedAttributes |= OpcUa_NodeAttributesMask_IsAbstract; uaNodeAttributes->IsAbstract = nodeAttributes.isAbstract(); } break; } case QOpcUa::NodeClass::View: { CREATE_NODE_ATTRIBUTE(View); if (nodeAttributes.hasContainsNoLoops()) { uaNodeAttributes->SpecifiedAttributes |= OpcUa_NodeAttributesMask_ContainsNoLoops; uaNodeAttributes->ContainsNoLoops = nodeAttributes.containsNoLoops(); } if (nodeAttributes.hasEventNotifier()) { uaNodeAttributes->SpecifiedAttributes |= OpcUa_NodeAttributesMask_EventNotifier; uaNodeAttributes->EventNotifier = nodeAttributes.eventNotifier(); } break; } default: qCWarning(QT_OPCUA_PLUGINS_UACPP) << "Could not convert node attributes, unknown node class"; return false; } return true; } QT_END_NAMESPACE