最近公司想把windows下软件迁移到linux下,在与plc通讯方面西门子只提供windows下的库,linux下没有对应库,幸好发现有open62541这个协议外接库,对应的plc最低型号为s1200,还必须升级固件才行。官方貌似有实例代码,但是看起来杂乱无章,现在整理一下。
很想直接上代码,但是先简单介绍一下,使用博图16版本进行配合使用,连接有两种方式,一种匿名连接,一种有名连接,根据需要自己选择。
同时我们必须要了解节点,NodeId 是节点的唯一编号,NodeClass 是节点类型,BrowseName用于浏览,DisplayName 是节点的名称,TypeDefinitionId是类型定义的唯一编号。我们所需要的是要知道节点的作用域以及节点编号,这个可以在博图16中看到,而且最多只能有500个点位,类似于snap7中的DB块之类的东西,然后响应时间之类的设置自己可以用博图软件修改。
下面上实现类代码的头文件:
这是对应我们公司所需的,open62541中是直接看节点的,节点类型有Numberic、string、StringAlloc、ByteString、GUID等类型,我司只用到Numberic类型,该类型下就对应int、dint、word、dword、bool、byte等数据类型的读写。
#ifndef CXNOPC_H
#define CXNOPC_H
#include "open62541.h"
#include <QString>
#include <QTime>
#include <vector>
#include <QThread>
#include <QFile>
#include <QTextStream>
#include <QDateTime>
using namespace std;
/*
* NodeId 是节点的唯一编号,NodeClass 是节点类型,BrowseName用于浏览,DisplayName 是节点的名称,TypeDefinitionId是类型定义的唯一编号。
*/
class CXNOpc : public QThread
{
Q_OBJECT
public:
CXNOpc(QObject *parent = 0);
~CXNOpc();
public:
bool InitXNOPC(QString qsUrl, QString qsLogFile, UA_ClientStateCallback stateCallback);
bool ConnectOPCByUserName(QString qsUserName, QString qsPassWord);
bool ConnectOPCByVisitor();
void* GetOpcValueNumberic(UA_UInt16 nsIndex, UA_UInt32 identifier, int Type, int& length);
bool SetOpcValueNumberic(UA_UInt16 nsIndex, UA_UInt32 identifier, int Type,void* value);
bool AddNumbericMonitor(UA_UInt16 nsIndex, vector<UA_UInt32> identifier, UA_Client_DataChangeNotificationCallback callback);
bool SetOpcMonitorTime(int time = 1);
bool StartMonitorTask();
bool StopMonitorTask();
QString OutLogTime();
protected:
void run();
private:
bool ReadOpcNumberic(UA_UInt16 nsIndex, UA_UInt32 identifier, UA_Variant *val);
bool ReadOpcString(UA_UInt16 nsIndex, char *chars, UA_Variant *val);
bool ReadOpcStringAlloc(UA_UInt16 nsIndex, const char *chars, UA_Variant *val);
bool ReadOpcGuid(UA_UInt16 nsIndex, UA_Guid guid, UA_Variant *val);
bool ReadOpcByteString(UA_UInt16 nsIndex, char *chars, UA_Variant *val);
bool ReadOpcByteStringAlloc(UA_UInt16 nsIndex, const char *chars, UA_Variant *val);
bool WriteOpcNumberic(UA_UInt16 nsIndex, UA_UInt32 identifier, UA_Variant *myVariant);
bool WriteOpcString(UA_UInt16 nsIndex, char *chars, UA_Variant *myVariant);
bool WriteOpcStringAlloc(UA_UInt16 nsIndex, const char *chars, UA_Variant *myVariant);
bool WriteOpcGuid(UA_UInt16 nsIndex, UA_Guid guid, UA_Variant *myVariant);
bool WriteOpcByteString(UA_UInt16 nsIndex, char *chars, UA_Variant *myVariant);
bool WriteOpcByteStringAlloc(UA_UInt16 nsIndex, const char *chars, UA_Variant *myVariant);
bool AddSubscription();
public:
QTextStream* m_qOutLog;
private:
bool m_bIsInit;
bool m_bIsConnect;
bool m_bStatus;
int m_time;
QFile* m_qLogFile;
QDateTime m_qLogTime;
QString m_qsUrl;
UA_UInt32 m_subId;
UA_Client* m_pClient;
};
#endif // CXNOPC_H
下面是对应的初始化客户端以及两种连接plc代码
#include "cxnopc.h"
#include <stdio.h>
#include <unistd.h>
#include <QDateTime>
#define print(format, ...) \
do \
{ \
printf(format, ##__VA_ARGS__); \
} while (0)
CXNOpc::CXNOpc(QObject *parent) : m_bIsInit(false),
m_bIsConnect(false),
m_bStatus(false),
m_time(1),
m_qLogFile(NULL),
m_qOutLog(NULL)
{
}
CXNOpc::~CXNOpc()
{
if(m_pClient != NULL && m_bIsConnect)
{
if(m_subId > 0)
UA_Client_Subscriptions_deleteSingle(m_pClient, m_subId);
UA_Client_disconnect(m_pClient);
UA_Client_delete(m_pClient);
}
if(m_bStatus)
{
terminate();
quit();
}
if(m_qLogFile != NULL)
{
m_qLogFile->close();
delete m_qLogFile;
}
if(m_qOutLog != NULL)
delete m_qOutLog;
}
QString CXNOpc::OutLogTime()
{
m_qLogTime = QDateTime::currentDateTime();
QString qStr = m_qLogTime.toString("yyy-MM-dd hh:mm::ss ddd");
return qStr;
}
bool CXNOpc::InitXNOPC(QString qsUrl, QString qsLogFile, UA_ClientStateCallback stateCallback)
{
if(m_bIsInit)
return m_bIsInit;
if( qsUrl.size() == 0 )
return m_bIsInit;
else
m_qsUrl = qsUrl;
m_qLogFile = new QFile(qsLogFile);
if (!m_qLogFile->open(QIODevice::QIODevice::WriteOnly | QIODevice::Text))
printf("Fail to open logfile\n");
else
m_qOutLog = new QTextStream(m_qLogFile);
QByteArray ba = qsUrl.toLatin1();
char* pchUrl = ba.data();
size_t szEndpointArraySize = 0;
UA_ClientConfig clentConfig = UA_ClientConfig_default;
clentConfig.stateCallback = stateCallback;
m_pClient = UA_Client_new(clentConfig);
UA_EndpointDescription* pEndpointArray = NULL;
//查询服务器节点,存储到结构体数组中UA_EndpointDescription
UA_StatusCode retvalue = UA_Client_getEndpoints(m_pClient, pchUrl,&szEndpointArraySize, &pEndpointArray);
if(retvalue != UA_STATUSCODE_GOOD)
{
UA_Array_delete(pEndpointArray, szEndpointArraySize, &UA_TYPES[UA_TYPES_ENDPOINTDESCRIPTION]);//删除获取的节点
UA_Client_delete(m_pClient);//删除客户端
return m_bIsInit;
}
//打印节点信息
printf("%i endpoints found\n", (int)szEndpointArraySize); //1 1 endpoints found
for(size_t i=0;i<szEndpointArraySize;i++)
printf("URL of endpoint %i is %.*s\n", (int)i, (int)pEndpointArray[i].endpointUrl.length,pEndpointArray[i].endpointUrl.data);
UA_Array_delete(pEndpointArray,szEndpointArraySize, &UA_TYPES[UA_TYPES_ENDPOINTDESCRIPTION]);
m_bIsInit = true;
return m_bIsInit;
}
bool CXNOpc::ConnectOPCByUserName(QString qsUserName, QString qsPassWord)
{
if(!m_bIsInit || m_bIsConnect)
return m_bIsConnect;
printf("%s-%s-%s\n",m_qsUrl.toLatin1().data(), qsUserName.toUtf8().data(), qsPassWord.toStdString().c_str());
UA_StatusCode retvalue = UA_Client_connect_username(m_pClient, m_qsUrl.toLatin1().data(), qsUserName.toUtf8().data(), qsPassWord.toStdString().c_str());
if(retvalue != UA_STATUSCODE_GOOD)
{
UA_Client_delete(m_pClient);
return m_bIsConnect;
}
//初始化 浏览请求
UA_BrowseRequest uaBrowReq;
UA_BrowseRequest_init(&uaBrowReq);
uaBrowReq.requestedMaxReferencesPerNode = 0;
uaBrowReq.nodesToBrowse = UA_BrowseDescription_new();
uaBrowReq.nodesToBrowseSize = 1;
uaBrowReq.nodesToBrowse[0].nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER); /* browse objects folder */
uaBrowReq.nodesToBrowse[0].resultMask = UA_BROWSERESULTMASK_ALL; /* return everything */
//浏览指定节点下NODE
UA_BrowseResponse uaBrowResp = UA_Client_Service_browse(m_pClient, uaBrowReq);
print("%-9s %-16s %-16s %-16s\n", "NAMESPACE", "NODEID", "BROWSE NAME", "DISPLAY NAME");
for(size_t i = 0; i < uaBrowResp.resultsSize; ++i)
{
for(size_t j = 0; j < uaBrowResp.results[i].referencesSize; ++j)
{
UA_ReferenceDescription *puaReferDescrip = &(uaBrowResp.results[i].references[j]);
if(puaReferDescrip->nodeId.nodeId.identifierType == UA_NODEIDTYPE_NUMERIC)
print("%-9d %-16d %-16.*s %-16.*s\n", puaReferDescrip->nodeId.nodeId.namespaceIndex,
puaReferDescrip->nodeId.nodeId.identifier.numeric, (int)puaReferDescrip->browseName.name.length,
puaReferDescrip->browseName.name.data, (int)puaReferDescrip->displayName.text.length,
puaReferDescrip->displayName.text.data);
else if(puaReferDescrip->nodeId.nodeId.identifierType == UA_NODEIDTYPE_STRING)
print("%-9d %-16.*s %-16.*s %-16.*s\n", puaReferDescrip->nodeId.nodeId.namespaceIndex,
(int)puaReferDescrip->nodeId.nodeId.identifier.string.length,
puaReferDescrip->nodeId.nodeId.identifier.string.data,
(int)puaReferDescrip->browseName.name.length, puaReferDescrip->browseName.name.data,
(int)puaReferDescrip->displayName.text.length, puaReferDescrip->displayName.text.data);
}
}
fflush(stdout);
UA_BrowseRequest_deleteMembers(&uaBrowReq);
UA_BrowseResponse_deleteMembers(&uaBrowResp);
m_bIsConnect = true;
return m_bIsConnect;
}
bool CXNOpc::ConnectOPCByVisitor()
{
if(!m_bIsInit || m_bIsConnect)
return m_bIsConnect;
UA_StatusCode retvalue = UA_Client_connect(m_pClient, m_qsUrl.toLatin1().data());
if(retvalue != UA_STATUSCODE_GOOD)
{
UA_Client_delete(m_pClient);
return m_bIsConnect;
}
//初始化 浏览请求
UA_BrowseRequest uaBrowReq;
UA_BrowseRequest_init(&uaBrowReq);
uaBrowReq.requestedMaxReferencesPerNode = 0;
uaBrowReq.nodesToBrowse = UA_BrowseDescription_new();
uaBrowReq.nodesToBrowseSize = 1;
uaBrowReq.nodesToBrowse[0].nodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER); /* browse objects folder */
uaBrowReq.nodesToBrowse[0].resultMask = UA_BROWSERESULTMASK_ALL; /* return everything */
//浏览指定节点下NODE
UA_BrowseResponse uaBrowResp = UA_Client_Service_browse(m_pClient, uaBrowReq);
print("%-9s %-16s %-16s %-16s\n", "NAMESPACE", "NODEID", "BROWSE NAME", "DISPLAY NAME");
for(size_t i = 0; i < uaBrowResp.resultsSize; ++i)
{
for(size_t j = 0; j < uaBrowResp.results[i].referencesSize; ++j)
{
UA_ReferenceDescription *puaReferDescrip = &(uaBrowResp.results[i].references[j]);
if(puaReferDescrip->nodeId.nodeId.identifierType == UA_NODEIDTYPE_NUMERIC)
print("%-9d %-16d %-16.*s %-16.*s\n", puaReferDescrip->nodeId.nodeId.namespaceIndex,
puaReferDescrip->nodeId.nodeId.identifier.numeric, (int)puaReferDescrip->browseName.name.length,
puaReferDescrip->browseName.name.data, (int)puaReferDescrip->displayName.text.length,
puaReferDescrip->displayName.text.data);
else if(puaReferDescrip->nodeId.nodeId.identifierType == UA_NODEIDTYPE_STRING)
print("%-9d %-16.*s %-16.*s %-16.*s\n", puaReferDescrip->nodeId.nodeId.namespaceIndex,
(int)puaReferDescrip->nodeId.nodeId.identifier.string.length,
puaReferDescrip->nodeId.nodeId.identifier.string.data,
(int)puaReferDescrip->browseName.name.length, puaReferDescrip->browseName.name.data,
(int)puaReferDescrip->displayName.text.length, puaReferDescrip->displayName.text.data);
}
}
fflush(stdout);
UA_BrowseRequest_deleteMembers(&uaBrowReq);
UA_BrowseResponse_deleteMembers(&uaBrowResp);
m_bIsConnect = true;
return m_bIsConnect;
}
剩下的读写点的方式后面再介绍,有需要的伙伴可以qq我 965434757