简介
1.为了方便与plc设备地址进行交互,通过KepServer连接plc设备,使用Qt制作OpcUa客户端读写KepServer数据。
2.代码依赖于open62541.c/.h, 和pthread,libwsock32,libws2_32库。LIBS += -lpthread libwsock32 libws2_32
核心代码
#ifndef OPCUA_H
#define OPCUA_H
#include <QObject>
#include <QTimer>
#include <QThread>
#include <QProcess>
#include "open62541.h"
struct OpcUaData {
QString clientUrl;
int namespaceIndex;
QString identifier_String; //my.plc.M101
bool isListening;
int listeningTimeMS; //
const UA_DataType *type; // &UA_TYPES[UA_TYPES_INT32]
OpcUaData ()
: clientUrl("opc.tcp://127.0.0.1:49320")
, namespaceIndex(2)
, identifier_String("")
, isListening(false)
, listeningTimeMS(1000)
{}
};
class OpcUA : public QObject
{
Q_OBJECT
public:
explicit OpcUA(OpcUaData opcData, QObject *parent = 0);
~OpcUA();
void setIdentifierString(QString item);
OpcUaData getOpcSetting();
signals:
void signalSendOpcItemData(QString itemId, const QVariant& value);
public slots:
void init();
void onListeningTimerTimeout();
bool onSetItem(QString itemId, const QVariant& value);
private:
void wait(int ms);
UA_Client* creationClient();
QVariant dataConvert(UA_Variant* variant);
private:
OpcUaData m_OpcUaData;
QTimer* m_ListeningTimer;
};
#endif // OPCUA_H
#include "opcua.h"
#include <QDebug>
#include <QElapsedTimer>
#include <QCoreApplication>
#include <windows.h>
static QVariant m_VarDataOld;
OpcUA::OpcUA(OpcUaData opcData, QObject *parent)
: QObject(parent)
, m_OpcUaData(opcData)
{
QThread* t = new QThread();
this->moveToThread(t);
connect(t, SIGNAL(started()), this, SLOT(init()));
t->start();
}
OpcUA::~OpcUA()
{
}
void OpcUA::setIdentifierString(QString item)
{
m_OpcUaData.identifier_String = item;
}
OpcUaData OpcUA::getOpcSetting()
{
return m_OpcUaData;
}
void OpcUA::init()
{
m_ListeningTimer = new QTimer();
connect(m_ListeningTimer, SIGNAL(timeout()), this, SLOT(onListeningTimerTimeout()));
m_ListeningTimer->setInterval(m_OpcUaData.listeningTimeMS);
if (m_OpcUaData.isListening) {
m_ListeningTimer->start();
}
}
void OpcUA::onListeningTimerTimeout()
{
UA_Variant* variant = UA_Variant_new();
char* data;
QByteArray ba = m_OpcUaData.identifier_String.toUtf8();
data = ba.data();
UA_Client* client = creationClient();
if (client == NULL) {
qDebug() << "onListeningTimerTimeout连接失败";
return;
}
UA_StatusCode retval = UA_Client_readValueAttribute(client, UA_NODEID_STRING(m_OpcUaData.namespaceIndex, data), variant);
if (retval == UA_STATUSCODE_GOOD && UA_Variant_isScalar(variant)) {
QVariant var = dataConvert(variant);
if (var != m_VarDataOld) {
m_VarDataOld = var;
emit signalSendOpcItemData(m_OpcUaData.identifier_String, var);
}
}
}
bool OpcUA::onSetItem(QString itemId, const QVariant &value)
{
if (itemId != m_OpcUaData.identifier_String) {
return false;
}
UA_Variant *myVariant = UA_Variant_new();
if (m_OpcUaData.type == &UA_TYPES[UA_TYPES_STRING]) {
std::string TestString = value.toString().toStdString();
unsigned char* DataChar = const_cast<unsigned char *>((const unsigned char*)TestString.c_str());
UA_String data;
data.data = DataChar;
data.length = value.toString().length();
UA_Variant_setScalarCopy(myVariant, &data, m_OpcUaData.type);
}
else {
UA_Variant_setScalarCopy(myVariant, &value, m_OpcUaData.type);
}
char *isting;
QByteArray ba = m_OpcUaData.identifier_String.toUtf8();
isting = ba.data();
UA_Client* client = creationClient();
if (client == NULL) {
return false;
}
UA_StatusCode retval = UA_Client_writeValueAttribute(client, UA_NODEID_STRING(m_OpcUaData.namespaceIndex, isting), myVariant);
UA_Client_delete(client);
if (retval != UA_STATUSCODE_GOOD) {
qDebug() << "写入失败状态";
return false;
}
else {
qDebug() << "写入成功";
return true;
}
return false;
}
void OpcUA::wait(int ms)
{
QElapsedTimer t;
t.start();
while (t.elapsed() < ms)
QCoreApplication::processEvents();
}
UA_Client *OpcUA::creationClient()
{
UA_Client* client = UA_Client_new(UA_ClientConfig_default);
UA_StatusCode retval = UA_Client_connect(client, m_OpcUaData.clientUrl.toUtf8().data());
if (retval != UA_STATUSCODE_GOOD) {
UA_Client_delete(client);
//连接失败进行多次重连
for (int i = 0; i < 10; i++) {
wait(100);
client = UA_Client_new(UA_ClientConfig_default);
retval = UA_Client_connect(client, m_OpcUaData.clientUrl.toUtf8().data());
if (retval == UA_STATUSCODE_GOOD) {
return client;
}
}
return NULL;
}
return client;
}
QVariant OpcUA::dataConvert(UA_Variant *variant)
{
if (m_OpcUaData.type == &UA_TYPES[UA_TYPES_BOOLEAN]) {
bool value = *(bool*)variant->data;
return QVariant(value);
}
else if(m_OpcUaData.type == &UA_TYPES[UA_TYPES_STRING]) {
UA_String value = *(UA_String*)variant->data;
QString str;
for (unsigned int i = 0; i < value.length; i++) {
str.append(QChar(value.data[i]));
}
return QVariant(str);
}
else if (m_OpcUaData.type == &UA_TYPES[UA_TYPES_INT32]) {
UA_UInt32 value = *(UA_UInt32*)variant->data;
return QVariant(value);
}
else if (m_OpcUaData.type == &UA_TYPES[UA_TYPES_INT64]) {
UA_UInt64 value = *(UA_UInt64*)variant->data;
return QVariant(value);
}
else if (m_OpcUaData.type == &UA_TYPES[UA_TYPES_DOUBLE]) {
double value = *(double*)variant->data;
return QVariant(value);
}
return QVariant();
}
总结
1.使用OpcUa客户端高频读写KepServer数据会导致服务经常挂掉,但是会立马恢复,所以才编写了重连机制,一直无法解决服务挂掉问题。错误代码0x80340000,0x80ae0000,0x80020000。如有解决方案,评论区请教大佬。
2.核心代码读写数据类型必须要匹配,否则会出现数据错误等问题。