linux/windows下基于opc ua协议使用open62541开发客户端-上

最近公司想把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

猜你喜欢

转载自blog.csdn.net/qq_42956179/article/details/104602338