#include "OpcUaManager.h" #include #include #include // OPC UA错误码常量定义 const qint32 bad_user_access_denied = -2139881472; // 用户访问被拒绝 const qint32 bad_type_mismatch = -2147483053; // 数据类型不匹配 OpcUaManager* OpcUaManager::m_instance = nullptr; // 初始化为空 QMutex OpcUaManager::m_mutex; // 初始化互斥锁 OpcUaManager::OpcUaManager(QObject *parent) : QObject(parent) , mClient(nullptr) , mCurrentNode(nullptr) , mReconnectTimer(new QTimer(this)) { connect(mReconnectTimer, &QTimer::timeout, this, &OpcUaManager::tryReconnect); } OpcUaManager* OpcUaManager::instance(QObject *parent) { // 双重检查锁:1. 先判断实例是否存在(避免频繁加锁);2. 加锁后再次判断(确保线程安全) if (!m_instance) { QMutexLocker locker(&m_mutex); // 加锁(自动解锁,避免死锁) if (!m_instance) { m_instance = new OpcUaManager(parent); // 创建唯一实例 } } return m_instance; } // 析构函数(原有逻辑不变) OpcUaManager::~OpcUaManager() { disconnectFromServer(); if (mClient) { delete mClient; mClient = nullptr; } } QOpcUaClient* OpcUaManager::getClient() const { return mClient; } // 连接到OPC UA服务器 bool OpcUaManager::connectToServer(const QString &url, const QString &username, const QString &password) { mServerUrl = QUrl(url); mUserName = username; mPassword = password; if (mClient) { disconnectFromServer(); delete mClient; mClient = nullptr; } createClient(); if (!mClient) { emit errorOccurred("无法创建OPC UA客户端"); // ✅ 启动重连定时器 onClientStateChanged(QOpcUaClient::Disconnected); return false; } mClient->requestEndpoints(mServerUrl); return true; } // 断开与服务器的连接 void OpcUaManager::disconnectFromServer() { if (mClient) { mClient->disconnectFromEndpoint(); // 断开端点连接 } // 清理当前节点 if (mCurrentNode) { mCurrentNode->disconnect(this); mCurrentNode = nullptr; } mCurrentNodeId.clear(); mPendingWriteValue = QVariant(); // 清空待写入值 } // 读取节点值 bool OpcUaManager::readNodeValue(const QString &nodeId) { // 检查客户端状态 if (!mClient || mClient->state() != QOpcUaClient::Connected) { emit errorOccurred("客户端未连接"); return false; } // 清理旧节点 if (mCurrentNode) { mCurrentNode->disconnect(this); mCurrentNode = nullptr; } // 获取目标节点 mCurrentNodeId = nodeId; mCurrentNode = mClient->node(nodeId); if (!mCurrentNode) { QString error = QString("无法获取节点: %1").arg(nodeId); emit errorOccurred(error); return false; } mCurrentNode->setParent(this); // 连接读取信号 connect(mCurrentNode, static_cast(&QOpcUaNode::attributeRead), this, &OpcUaManager::onAttributeRead); // 读取节点值和访问级别 mCurrentNode->readAttributes( QOpcUa::NodeAttribute::Value | // 节点值 QOpcUa::NodeAttribute::AccessLevel // 访问级别(可读/可写) ); return true; } // 写入节点值(动态适配类型) bool OpcUaManager::writeNodeValue(const QString &nodeId, const QVariant &value) { // 检查客户端状态 if (!mClient || mClient->state() != QOpcUaClient::Connected) { emit errorOccurred("客户端未连接"); return false; } // 清理旧节点 if (mCurrentNode) { mCurrentNode->disconnect(this); mCurrentNode = nullptr; } // 保存待写入值和节点ID mPendingWriteValue = value; mCurrentNodeId = nodeId; // 获取目标节点 mCurrentNode = mClient->node(nodeId); if (!mCurrentNode) { QString error = QString("无法获取节点: %1").arg(nodeId); emit errorOccurred(error); return false; } mCurrentNode->setParent(this); // 连接类型读取信号(写入前先读类型) connect(mCurrentNode, static_cast(&QOpcUaNode::attributeRead), this, &OpcUaManager::onPreWriteValueTypeRead); // 读取节点当前值(用于判断类型) mCurrentNode->readAttributes(QOpcUa::NodeAttribute::Value); return true; } // 获取当前连接状态 QOpcUaClient::ClientState OpcUaManager::connectionState() const { if (mClient) { return mClient->state(); } return QOpcUaClient::Disconnected; } // 创建OPC UA客户端 void OpcUaManager::createClient() { QOpcUaProvider provider; QMap clientParams; // 设置认证信息 if (!mUserName.isEmpty() && !mPassword.isEmpty()) { clientParams["username"] = mUserName; clientParams["password"] = mPassword; } // 禁用安全策略(解决部分服务器权限误判) clientParams["securityMode"] = "None"; clientParams["securityPolicy"] = "None"; // 创建客户端(使用open62541后端,兼容性较好) mClient = provider.createClient("open62541", clientParams); if (!mClient) { return; } mClient->setParent(this); // 连接客户端信号 connect(mClient, static_cast, QOpcUa::UaStatusCode, QUrl)>(&QOpcUaClient::endpointsRequestFinished), this, &OpcUaManager::onEndpointsRequestFinished); connect(mClient, static_cast(&QOpcUaClient::stateChanged), this, &OpcUaManager::onClientStateChanged); } uint32_t mReconnectCount =0; void OpcUaManager::tryReconnect() { if (mClient && mClient->state() == QOpcUaClient::Disconnected) { qDebug() << "正在执行第" << mReconnectCount << "次重连..."; connectToServer(mServerUrl.toString(), mUserName, mPassword); } mReconnectCount ++; } // 端点请求完成处理 void OpcUaManager::onEndpointsRequestFinished(QVector endpoints, QOpcUa::UaStatusCode statusCode, QUrl requestedUrl) { Q_UNUSED(requestedUrl); if (statusCode != QOpcUa::UaStatusCode::Good || endpoints.isEmpty()) { QString error = QString("获取端点失败,状态码: %1").arg(static_cast(statusCode)); emit errorOccurred(error); // ✅ 启动重连定时器 mReconnectTimer->start(RECONNECT_INTERVAL); return; } mClient->connectToEndpoint(endpoints.first()); } // 客户端状态变化处理 void OpcUaManager::onClientStateChanged(QOpcUaClient::ClientState state) { emit stateChanged(state); // 转发状态变化信号 if (state == QOpcUaClient::Disconnected) { // 无限重连 qDebug() << "OPC UA 连接断开," << RECONNECT_INTERVAL << "ms 后自动重连..."; mReconnectTimer->start(RECONNECT_INTERVAL); } else if (state == QOpcUaClient::Connected) { mReconnectTimer->stop(); emit reconnected(); // 广播重连成功 } } // 属性读取完成处理(普通读取) void OpcUaManager::onAttributeRead(QOpcUa::NodeAttributes attrs) { if (attrs.testFlag(QOpcUa::NodeAttribute::Value) && mCurrentNode) { // 读取值并转发 QVariant value = mCurrentNode->attribute(QOpcUa::NodeAttribute::Value); emit readCompleted(value, mCurrentNodeId); // // 检查访问级别 // if (attrs.testFlag(QOpcUa::NodeAttribute::AccessLevel)) { // quint8 accessLevel = mCurrentNode->attribute(QOpcUa::NodeAttribute::AccessLevel).toUInt(); // bool canWrite = (accessLevel & 0x02); // 0x02 = 可写 // bool canRead = (accessLevel & 0x01); // 0x01 = 可读 // qDebug() << "节点访问级别: " << accessLevel // << ",可读: " << canRead // << ",可写: " << canWrite; // if (!canWrite) { // emit errorOccurred("节点当前不可写(服务器限制)"); // } // } } else { emit readCompleted(QVariant(), mCurrentNodeId); emit errorOccurred(QString("读取节点 %1 失败").arg(mCurrentNodeId)); } } // 写入前读取值类型(使用QOpcUa::Types枚举强制指定类型) void OpcUaManager::onPreWriteValueTypeRead(QOpcUa::NodeAttributes attrs) { if (!mCurrentNode || !attrs.testFlag(QOpcUa::NodeAttribute::Value)) { emit errorOccurred("读取节点当前值失败,无法判断类型"); return; } // 断开类型读取信号(避免重复触发) disconnect(mCurrentNode, &QOpcUaNode::attributeRead, this, &OpcUaManager::onPreWriteValueTypeRead); // 获取当前值的类型 QVariant currentValue = mCurrentNode->attribute(QOpcUa::NodeAttribute::Value); QString typeName = currentValue.typeName(); //qDebug() << "节点当前值类型:" << typeName; // 转换值并指定QOpcUa类型枚举(核心修复) QVariant convertedValue; QOpcUa::Types targetType = QOpcUa::Types::Undefined; // 使用枚举类型 if (typeName == "int") { convertedValue = mPendingWriteValue.toInt(); targetType = QOpcUa::Types::Int32; // int对应Int32 } else if (typeName == "float") { convertedValue = static_cast(mPendingWriteValue.toDouble()); targetType = QOpcUa::Types::Float; // float对应Float } else if (typeName == "double") { convertedValue = mPendingWriteValue.toDouble(); targetType = QOpcUa::Types::Double; // double对应Double } else if (typeName == "ushort") { quint16 value = static_cast(mPendingWriteValue.toUInt()); if (value > 65535) { value = 65535; qWarning() << "值超过ushort最大值65535,已自动截断"; } convertedValue = value; targetType = QOpcUa::Types::UInt16; // ushort对应UInt16 } else { convertedValue = mPendingWriteValue; targetType = QOpcUa::Types::Undefined; // 自动类型 } //qDebug() << "转换为" << typeName << "类型写入,QOpcUa枚举值:" << static_cast(targetType); // 连接写入信号并执行写入 connect(mCurrentNode, static_cast(&QOpcUaNode::attributeWritten), this, &OpcUaManager::onAttributeWritten); // 关键:使用QOpcUa::Types枚举作为参数,解决类型不匹配 mCurrentNode->writeAttribute( QOpcUa::NodeAttribute::Value, convertedValue, targetType ); } #include #include #include #include bool OpcUaManager::writeNodeValue(const QString &nodeId, const QVariant &value, int maxRetry /*= 3*/, int retryIntervalMs /*= 200*/) { if (!mClient || mClient->state() != QOpcUaClient::Connected) { qWarning().noquote() << "[OPC] 客户端未连接,写入" << nodeId << "失败"; return false; } QOpcUaNode *node = mClient->node(nodeId); if (!node) { qWarning().noquote() << "[OPC] 无法获取节点:" << nodeId; return false; } for (int attempt = 1; attempt <= maxRetry; ++attempt) { /* ---------- 1. 先读当前值 ---------- */ QEventLoop readLoop; bool readOk = false; connect(node, &QOpcUaNode::attributeRead, [&](QOpcUa::NodeAttributes) { readOk = true; readLoop.quit(); }); node->readAttributes(QOpcUa::NodeAttribute::Value); QTimer::singleShot(2000, &readLoop, &QEventLoop::quit); readLoop.exec(); if (!readOk) { qWarning().noquote() << "[OPC] 第" << attempt << "次读取超时:" << nodeId; continue; } QVariant cur = node->attribute(QOpcUa::NodeAttribute::Value); if (!cur.isValid()) { qWarning().noquote() << "[OPC] 第" << attempt << "次读到无效值:" << nodeId; continue; } /* ---------- 2. 类型转换 ---------- */ QVariant v = value; QOpcUa::Types typeHint = QOpcUa::Types::Undefined; QString tn = cur.typeName(); if (tn == "int") { v.convert(QMetaType::Int); typeHint = QOpcUa::Types::Int32; } else if (tn == "float") { v.convert(QMetaType::Float); typeHint = QOpcUa::Types::Float; } else if (tn == "double"){ v.convert(QMetaType::Double); typeHint = QOpcUa::Types::Double; } else if (tn == "ushort"){ v.convert(QMetaType::UShort); typeHint = QOpcUa::Types::UInt16; } /* ---------- 3. 同步写 ---------- */ struct WriteWait : QObject { bool ok = false; QEventLoop loop; } waiter; connect(node, static_cast( &QOpcUaNode::attributeWritten), &waiter, [&waiter](QOpcUa::NodeAttribute, QOpcUa::UaStatusCode code) mutable { waiter.ok = (code == QOpcUa::UaStatusCode::Good); waiter.loop.quit(); }, Qt::QueuedConnection); node->writeAttribute(QOpcUa::NodeAttribute::Value, v, typeHint); QTimer::singleShot(2000, &waiter.loop, &QEventLoop::quit); waiter.loop.exec(); if (waiter.ok) { return true; } /* ---------- 4. 失败日志 & 重试 ---------- */ qWarning().noquote() << "[OPC] 第" << attempt << "/" << maxRetry << "次写入失败:" << nodeId << " 等待重试 ..."; if (attempt < maxRetry) QThread::msleep(retryIntervalMs); } qCritical().noquote() << "[OPC] 最终写入失败:" << nodeId; return false; } // 属性写入完成处理 void OpcUaManager::onAttributeWritten(QOpcUa::NodeAttribute attribute, QOpcUa::UaStatusCode statusCode) { if (attribute == QOpcUa::NodeAttribute::Value) { bool success = (statusCode == QOpcUa::UaStatusCode::Good); emit writeCompleted(success, mCurrentNodeId); if (!success) { QString errorMsg; // 根据错误码解析原因 switch (static_cast(statusCode)) { case bad_user_access_denied: errorMsg = QString("写入节点 %1 失败:用户没有写入权限(可能是类型不匹配导致的误报)").arg(mCurrentNodeId); break; case bad_type_mismatch: errorMsg = QString("写入节点 %1 失败:数据类型不匹配").arg(mCurrentNodeId); break; default: errorMsg = QString("写入节点 %1 失败,状态码: %2") .arg(mCurrentNodeId) .arg(static_cast(statusCode)); } emit errorOccurred(errorMsg); } } }