Files
EJM_Display/DataCenter/OpcUaManager - 副本.cpp
2025-09-15 22:28:43 +08:00

451 lines
15 KiB
C++
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "OpcUaManager.h"
#include <QOpcUaProvider>
#include <QMap>
#include <QDebug>
// 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<void (QOpcUaNode::*)(QOpcUa::NodeAttributes)>(&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<void (QOpcUaNode::*)(QOpcUa::NodeAttributes)>(&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<QString, QVariant> 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<void (QOpcUaClient::*)(QVector<QOpcUaEndpointDescription>,
QOpcUa::UaStatusCode,
QUrl)>(&QOpcUaClient::endpointsRequestFinished),
this,
&OpcUaManager::onEndpointsRequestFinished);
connect(mClient,
static_cast<void (QOpcUaClient::*)(QOpcUaClient::ClientState)>(&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<QOpcUaEndpointDescription> endpoints,
QOpcUa::UaStatusCode statusCode,
QUrl requestedUrl)
{
Q_UNUSED(requestedUrl);
if (statusCode != QOpcUa::UaStatusCode::Good || endpoints.isEmpty()) {
QString error = QString("获取端点失败,状态码: %1").arg(static_cast<int>(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<float>(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<quint16>(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<int>(targetType);
// 连接写入信号并执行写入
connect(mCurrentNode,
static_cast<void (QOpcUaNode::*)(QOpcUa::NodeAttribute, QOpcUa::UaStatusCode)>(&QOpcUaNode::attributeWritten),
this,
&OpcUaManager::onAttributeWritten);
// 关键使用QOpcUa::Types枚举作为参数解决类型不匹配
mCurrentNode->writeAttribute(
QOpcUa::NodeAttribute::Value,
convertedValue,
targetType
);
}
#include <QDebug>
#include <QEventLoop>
#include <QTimer>
#include <QThread>
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<void(QOpcUaNode::*)(QOpcUa::NodeAttribute, QOpcUa::UaStatusCode)>(
&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<qint32>(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<int>(statusCode));
}
emit errorOccurred(errorMsg);
}
}
}