Files
EJM_Display/DataCenter/OpcUaManager.cpp
2025-08-20 23:06:28 +08:00

351 lines
12 KiB
C++
Raw 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(QObject *parent)
: QObject(parent)
, mClient(nullptr)
, mCurrentNode(nullptr)
, mReconnectTimer(new QTimer(this))
{
connect(mReconnectTimer, &QTimer::timeout, // 3. 铃声(timeout)连到槽
this, &OpcUaManager::tryReconnect); // 到时就会执行 tryReconnect()
}
// 析构函数
OpcUaManager::~OpcUaManager()
{
disconnectFromServer();
if (mClient) {
delete mClient;
mClient = nullptr;
}
}
// 连接到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
);
}
// 属性写入完成处理
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);
}
}
}