【opcua】从编译文件到客户端的收发、断连、节点查询等实现

【opcua】从编译文件到客户端的收发、断连、节点查询等实现

  • 前言
  • opcua编译文件
  • opcua客户端编写
    • 前期调试工具准备
    • 客户端功能编写
      • .pro文件导入文件
      • .h文件全局变量定义
      • 所连服务器端点查看
      • 连接服务器
      • 断连服务器
      • 节点查看(整型+字符串节点)
      • 节点与地址匹配
      • 读单节点(int16、int32、bool、string、float)
      • 写单节点(int16、int32、bool、string、float)
      • 读多节点(int16、int32、bool、string、float)
      • 写多节点(int16、int32、bool、string、float)
  • KEPServerEX配置

前言

在进行opcua前,需要先下载opcua库,由于最终需要用于qt,因此有2个选择:用qt 自带的opcua 库,以及open62541库,博主是把2种都下载尝试了,考虑到封装的便捷性,最终采用open62541库。因此,本博客主要介绍open62541库的编译+使用,qt自带的库安装可参考大神们的资料:Qt添加QOpcUa类

opcua编译文件

一、从open62541 官网 下载最新版本的源文件Source Code(zip)
二、下载Visual StudioPythoncmake-gui
博主的版本如下:
open62541-1.3
MSVC2015 64bit
python-3.12.1-amd64
cmake-3.28.0-windows-x86_64
三、进行文件编译,最终生成3个编译文件,如图所示,编译过程可参照学习open62541 — [19] 使用Visual Studio编译及使用open62541
【opcua】从编译文件到客户端的收发、断连、节点查询等实现_第1张图片

opcua客户端编写

前期调试工具准备

在客户端编写前,建议先下载opcua 服务器KEPServerEX,内含客户端,但是如果需要验证加密连接功能,还需要下载第三方opcua客户端uaexpert-bin-win32;如果不考虑加密连接,为了方便查看所有节点,可下载第三方opcua客户端OPCUAClient。如图所示:
在这里插入图片描述

客户端功能编写

在编写过程中,推荐看所下载open62541-1.3 中所提供的官方示例open62541-1.3\examples\client.c,可满足简单的基本需求,如下功能实现是根据示例以及编译生成文件的基础上,进行封装扩展的功能

.pro文件导入文件

导入ws2_32库(原因是:open62541使用socket,而windows下的socket要依赖WS2_32.lib。),以及编译生成的文件

LIBS += -lws2_32

SOURCES += \
    open62541.c
HEADERS += \
    open62541.h
    
INCLUDEPATH += $$PWD
unix|win32: LIBS += -L$$PWD/ -lopen62541

.h文件全局变量定义

public:
    enum ValueType{
        Bool,
        Int16,
        Int32,
        Float,
        String
    };
private:
	UA_Client *client;
	QMap<QString,QString> nodeValues;

所连服务器端点查看

void MainWindow::on_endpoints_clicked()
{
    listing_endpoints("opc.tcp://10.60.45.208:49322");
}

//所连服务器端点查看
void MainWindow::listing_endpoints(const char *serverUrl)
{
    /* Listing endpoints */
    //单独创建客户端,避免连接状态的客户端去查询其他服务器ip的端点
    UA_Client *client2 = UA_Client_new();
    UA_ClientConfig_setDefault(UA_Client_getConfig(client2));
    UA_EndpointDescription* endpointArray = NULL;
    size_t endpointArraySize = 0;
    UA_StatusCode retval = UA_Client_getEndpoints(client2, serverUrl,
                                                  &endpointArraySize, &endpointArray);
    if(retval != UA_STATUSCODE_GOOD) {
        UA_Array_delete(endpointArray, endpointArraySize, &UA_TYPES[UA_TYPES_ENDPOINTDESCRIPTION]);
        UA_Client_delete(client2);
        qDebug()<< "Listing endpoints failure"<<EXIT_FAILURE;
    }
    printf("%i endpoints found\n", (int)endpointArraySize);
    for(size_t i=0;i<endpointArraySize;i++) {
        printf("URL of endpoint %i is %.*s\n", (int)i,
               (int)endpointArray[i].endpointUrl.length,
               endpointArray[i].endpointUrl.data);
    }
    UA_Array_delete(endpointArray,endpointArraySize, &UA_TYPES[UA_TYPES_ENDPOINTDESCRIPTION]);
    UA_Client_delete(client2);
}

连接服务器

void MainWindow::on_connect_clicked()
{
    connect_to_server("opc.tcp://127.0.0.1:49320");
}
int MainWindow::connect_to_server(const char *endpointUrl)
{
    /* Connect to a server */
    client = UA_Client_new();
//    UA_ClientConfig_setDefault(UA_Client_getConfig(client));
    UA_ClientConfig *cc = UA_Client_getConfig(client);
    QDateTime current_date_time =QDateTime::currentDateTime();
    UA_ClientConfig_setDefault(cc);
    cc->timeout=60000;//表示连接成功了,但是发了之后没有响应所设置的时间,然后UA_STATUSCODE_BADTIMEOUT
    UA_StatusCode retval = UA_Client_connect(client, endpointUrl);
    if(retval != UA_STATUSCODE_GOOD) {
         qDebug()  <<  retval;
        return EXIT_FAILURE;
    }
    qDebug() << "success";
    return EXIT_SUCCESS;
}

断连服务器

void MainWindow::on_disconnect_clicked()
{
    disconnect_to_server();
}
int MainWindow::disconnect_to_server()
{
    UA_StatusCode retval=UA_Client_disconnect(client);
    if(retval != UA_STATUSCODE_GOOD) {
        qDebug() << "disconnect failure";
//        UA_Client_delete(client);
//        client =nullptr;
        return EXIT_FAILURE;
    }
//    UA_Client_delete(client);
//    client =nullptr;
    qDebug() << "disconnect success";
    return EXIT_SUCCESS;
}

节点查看(整型+字符串节点)

void MainWindow::on_Browse_nodes_number_clicked()
{
    browse_nodes(0, UA_NS0ID_OBJECTSFOLDER);
    browse_nodes(2,"my.plc.mygroup");
}
void MainWindow::browse_nodes(UA_UInt16 nsIndex, UA_UInt32 identifier)
{
    browse_nodes(UA_NODEID_NUMERIC(nsIndex, identifier));
}

void MainWindow::browse_nodes(UA_UInt16 nsIndex,const QString& identifier)
{
    QByteArray arr = identifier.toUtf8();
    char * cstr=arr.data();
    qDebug()<< nsIndex << cstr;
    browse_nodes(UA_NODEID_STRING(nsIndex, cstr));
}
void MainWindow::browse_nodes(UA_NodeId nodeId)
{
    /* Browse some objects */
    printf("Browsing nodes in objects folder:\n");
    UA_BrowseRequest bReq;
    UA_BrowseRequest_init(&bReq);
    bReq.requestedMaxReferencesPerNode = 0;//限制查到的最大节点数,0 不限制
    bReq.nodesToBrowse = UA_BrowseDescription_new();
    bReq.nodesToBrowseSize = 1;//需要浏览的节点个数
    bReq.nodesToBrowse[0].browseDirection =   UA_BROWSEDIRECTION_FORWARD;
    bReq.nodesToBrowse[0].nodeId = nodeId;
    bReq.nodesToBrowse[0].resultMask = UA_BROWSERESULTMASK_ALL; /* return everything */
    UA_BrowseResponse bResp = UA_Client_Service_browse(client, bReq);
    printf("%-9s %-16s %-16s %-16s\n", "NAMESPACE", "NODEID", "BROWSE NAME", "DISPLAY NAME");
    for(size_t i = 0; i < bResp.resultsSize; ++i) {
        for(size_t j = 0; j < bResp.results[i].referencesSize; ++j) {
            UA_ReferenceDescription *ref = &(bResp.results[i].references[j]);
            if(ref->nodeId.nodeId.identifierType == UA_NODEIDTYPE_NUMERIC) {
                printf("N%-9u %-16u %-16.*s %-16.*s\n", ref->nodeId.nodeId.namespaceIndex,
                       ref->nodeId.nodeId.identifier.numeric, (int)ref->browseName.name.length,
                       ref->browseName.name.data, (int)ref->displayName.text.length,
                       ref->displayName.text.data);
            } else if(ref->nodeId.nodeId.identifierType == UA_NODEIDTYPE_STRING) {
                printf("S%-9u %-16.*s %-16.*s %-16.*s %-16.*s\n", ref->nodeId.nodeId.namespaceIndex,
                       (int)ref->nodeId.nodeId.identifier.string.length,
                       ref->nodeId.nodeId.identifier.string.data,
                       (int)ref->browseName.name.length, ref->browseName.name.data,
                       (int)ref->displayName.text.length, ref->displayName.text.data);
            }
            /* TODO: distinguish further types */
        }
    }
    qDebug()<<"1xxxxxxxxxxxxxxxxxxxxxx";
    //UA_BrowseRequest_clear(&bReq);
    qDebug()<<"2xxxxxxxxxxxxxxxxxxxxxx";
    UA_BrowseResponse_clear(&bResp);
}

节点与地址匹配

该功能来自于无意间对节点地址的读取,当将节点my.plc.mygroup.x1my.plc.mygroup.x1._Address作为节点传入读单节点的方法,以String作为获取数据类型,会发现最后读到的结果是my.plc.mygroup.x1对应的地址值B0001
而封装该功能的目的是,当传值是节点对应的地址值时,依旧可实现节点的读写。

void MainWindow::nodeMap()
{
    UA_BrowseRequest bReq;
    UA_BrowseRequest_init(&bReq);
    bReq.requestedMaxReferencesPerNode = 0;//限制查到的最大节点数,0 不限制
    bReq.nodesToBrowse = UA_BrowseDescription_new();
    bReq.nodesToBrowseSize = 1;//需要浏览的节点个数
    bReq.nodesToBrowse[0].browseDirection =   UA_BROWSEDIRECTION_FORWARD;
    bReq.nodesToBrowse[0].nodeId = UA_NODEID_STRING(2, (char*)"my.plc.mygroup");
    bReq.nodesToBrowse[0].resultMask = UA_BROWSERESULTMASK_ALL; /* return everything */
    UA_BrowseResponse bResp = UA_Client_Service_browse(client, bReq);
    for(size_t i = 0; i < bResp.resultsSize; ++i) {
        for(size_t j = 0; j < bResp.results[i].referencesSize; ++j) {
            UA_ReferenceDescription *ref = &(bResp.results[i].references[j]);
            if(ref->nodeId.nodeId.identifierType == UA_NODEIDTYPE_STRING) {
                QString s;
                for(int i=0;i<ref->nodeId.nodeId.identifier.string.length;i++){
                    s.append(*(ref->nodeId.nodeId.identifier.string.data+i));
                }
                qDebug() << s;
                QVariant data;
                plc_get_ascii(client,2,QString("%1._Address").arg(s).toUtf8().data(),data,UA_TYPES_STRING);
                nodeValues[data.toString()]=s;
            }
        }
    }
    UA_BrowseResponse_clear(&bResp);
    for (auto iter = nodeValues.begin(); iter != nodeValues.end(); ++iter) {
        qDebug()<< iter.key() << " " << iter.value() ;
    }
}

读单节点(int16、int32、bool、string、float)

注释内容为官方提供示例,也可用。

void MainWindow::on_read_clicked()
{
    read_node();
}
void MainWindow::read_node()
{
    /* Read attribute */
//    UA_Boolean value = 0;
//    UA_Variant *val = UA_Variant_new();
//    UA_StatusCode retval = UA_Client_readValueAttribute(client, UA_NODEID_STRING(2, (char *)"my.plc.mygroup.x1"), val);
//    if(retval == UA_STATUSCODE_GOOD && val->type == &UA_TYPES[UA_TYPES_BOOLEAN]) {
//        value = *(UA_Boolean*)val->data;
//        printf("the value is: %i\n", value);
//    }
//    UA_Variant_delete(val);

    //example
    QVariant data;
    plc_get(2,(char *)"my.plc.mygroup.x1._Address",data,ValueType::String);
    qDebug()<< "the"<<data;
    plc_get(2,(char *)"my.plc.mygroup.x2",data,ValueType::Int16);
    qDebug()<< "the"<<data;
    plc_get(2,(char *)"my.plc.mygroup.y1",data,ValueType::Int32);
    qDebug()<< "the"<<data;
    plc_get(2,(char *)"my.plc.mygroup.y2",data,ValueType::Float);
    qDebug()<< "the"<<data;
    plc_get(2,(char *)"my.plc.mygroup.s1",data,ValueType::String);
    qDebug()<< "the"<<data;
}
template <class T> bool read_node_m(UA_Client *client,int nsIndex, char *identifier, QVariant &data, int uaType)
{
    UA_Variant *val = UA_Variant_new();
    UA_StatusCode retval = UA_Client_readValueAttribute(client, UA_NODEID_STRING(nsIndex, identifier), val);
    if(retval != UA_STATUSCODE_GOOD){
        qWarning()<<QString("PLC读取失败:%1-%2").arg(nsIndex).arg(identifier);
        UA_Variant_delete(val);
        return false;
    }
    if(val->type != &UA_TYPES[uaType]){
        qWarning()<<QString("PLC读取数据与获取类型不符");
        UA_Variant_delete(val);
        return false;
    }
    data = *(T*)val->data;
    UA_Variant_delete(val);
    return true;
}

bool MainWindow::plc_get(int nsIndex, char *identifier, QVariant &data, MainWindow::ValueType type)
{
    switch (type) {
    case ValueType::Bool:
        return read_node_m<UA_Boolean>(client,nsIndex,identifier,data,UA_TYPES_BOOLEAN);
    case ValueType::Float:
        return read_node_m<UA_Float>(client,nsIndex,identifier,data,UA_TYPES_FLOAT);
    case ValueType::Int16:
        return read_node_m<UA_Int16>(client,nsIndex,identifier,data,UA_TYPES_INT16);
    case ValueType::Int32:
        return read_node_m<UA_Int32>(client,nsIndex,identifier,data,UA_TYPES_INT32);
    case ValueType::String:
        return plc_get_ascii(client,nsIndex,identifier,data,UA_TYPES_STRING);
    default:
        data.clear();
        qWarning()<<QString("PLC读取不支持的类型:%1").arg(type);
        break;
    }
    return false;
}

bool MainWindow::plc_get_ascii(UA_Client *client,int nsIndex, char *identifier, QVariant &data, int type)
{
    UA_Variant *val = UA_Variant_new();
    UA_StatusCode retval = UA_Client_readValueAttribute(client, UA_NODEID_STRING(nsIndex, identifier), val);
    if(retval == UA_STATUSCODE_BADCONNECTIONCLOSED){
        qDebug() << "-----------reconnect";
    }
    if(retval != UA_STATUSCODE_GOOD){
        qWarning()<<QString("PLC读取失败:%1-%2").arg(nsIndex).arg(identifier);
        UA_Variant_delete(val);
        return false;
    }
    if(val->type != &UA_TYPES[type]){
        qWarning()<<QString("PLC读取数据与获取类型不符");
        UA_Variant_delete(val);
        return false;
    }
    UA_String resp= *(UA_String*)val->data;
    data=plc_8toAscii_8(resp,ascii_dh);
    UA_Variant_delete(val);
    return true;
}

QString MainWindow::plc_8toAscii_8(UA_String &resp, bool bh)
{
    QString s;
    for(int i=0;i<resp.length;i++){
        s.append(*(resp.data+i));
    }
    return s;
}

写单节点(int16、int32、bool、string、float)

注释内容为官方提供示例,也可用。

void MainWindow::on_write_clicked()
{
    write_node();
}
void MainWindow::write_node()
{
    /* Write node attribute (using the highlevel API) */
//    UA_Boolean value=1;
//    UA_Variant *myVariant = UA_Variant_new();
//    UA_Variant_setScalarCopy(myVariant, &value, &UA_TYPES[UA_TYPES_BOOLEAN]);
//    UA_StatusCode retval1 = UA_Client_writeValueAttribute(client, UA_NODEID_STRING(2, (char *)"my.plc.mygroup.x1"), myVariant);
//    if(retval1 == UA_STATUSCODE_GOOD ) {
//        printf("write success");
//    }
//    UA_Variant_delete(myVariant);
    //example
    plc_set(2,(char *)"my.plc.mygroup.x1",1,ValueType::Bool);
    plc_set(2,(char *)"my.plc.mygroup.x2",-12345,ValueType::Int16);
    plc_set(2,(char *)"my.plc.mygroup.y1",-12345,ValueType::Int32);
    plc_set(2,(char *)"my.plc.mygroup.y2",-2.24,ValueType::Float);
    plc_set(2,(char *)"my.plc.mygroup.s1","B12345678900987654321",ValueType::String);
}
bool MainWindow::write_mode_m(UA_Client *client,int nsIndex, char *identifier, const QVariant &data, int uaType)
{
    UA_Variant *myVariant = UA_Variant_new();
    UA_StatusCode retval;
    retval= UA_Variant_setScalarCopy(myVariant, &data, &UA_TYPES[uaType]);
    if(retval != UA_STATUSCODE_GOOD ) {
        qWarning()<<QString("写入数据转换失败");
        UA_Variant_delete(myVariant);
        return false;
    }
    retval = UA_Client_writeValueAttribute(client, UA_NODEID_STRING(nsIndex, identifier), myVariant);
    if(retval != UA_STATUSCODE_GOOD ) {
        qWarning()<<QString("PLC写入失败:%1-%2").arg(nsIndex).arg(identifier);
        UA_Variant_delete(myVariant);
        return false;
    }
    UA_Variant_delete(myVariant);
    qDebug()<< data <<"write success";
    return true;
}

bool MainWindow::plc_set_ascii(UA_Client *client, int nsIndex, char *identifier, const QString &data, int uaType)
{
    QByteArray arr = data.toUtf8();
    char * cstr=arr.data();
    UA_String uastr;
    uastr.data=(UA_Byte*) cstr;
    uastr.length=data.length();
    UA_Variant *myVariant = UA_Variant_new();
    UA_StatusCode retval;
    retval = UA_Variant_setScalarCopy(myVariant, &uastr, &UA_TYPES[uaType]);
    if(retval != UA_STATUSCODE_GOOD ) {
        qWarning()<<QString("写入数据转换失败");
        UA_Variant_delete(myVariant);
        return false;
    }
    retval = UA_Client_writeValueAttribute(client, UA_NODEID_STRING(nsIndex, identifier), myVariant);
    if(retval != UA_STATUSCODE_GOOD ) {
        qWarning()<<QString("PLC写入失败:%1-%2").arg(nsIndex).arg(identifier);
        UA_Variant_delete(myVariant);
        return false;
    }
    UA_Variant_delete(myVariant);
    qDebug()<< data <<"write success";
    return true;
}

bool MainWindow::plc_set(int nsIndex, char *identifier, const QVariant &data, ValueType type)
{
    switch (type) {
    case ValueType::Bool:
        return write_mode_m(client,nsIndex,identifier,data,UA_TYPES_BOOLEAN);
    case ValueType::Float:
        return write_mode_m(client,nsIndex,identifier,data.toFloat(),UA_TYPES_FLOAT);
    case ValueType::Int16:
        return write_mode_m(client,nsIndex,identifier,data,UA_TYPES_INT16);
    case ValueType::Int32:
        return write_mode_m(client,nsIndex,identifier,data,UA_TYPES_INT32);
    case ValueType::String:
        return plc_set_ascii(client,nsIndex,identifier,data.toString(),UA_TYPES_STRING);
    default:
        qWarning()<<QString("PLC写入不支持的类型:%1").arg(type);
        break;
    }
    return false;
}

读多节点(int16、int32、bool、string、float)

其实读单节点的UA_Client_readValueAttribute底层就是用UA_Client_Service_read去实现,只不过前者进行了简单封装,所以实现单节点读取,该节封装方法也可用。
注释内容为固定节点的使用,也可用。在编写动态节点的过程中,需要注意地址值->节点的转换,由于以for循环形式实现,一定要考虑临时变量回收的问题,由QString->char *,中间涉及到QByteArray,而QByteArray作为临时变量传地址给指针,如果QByteArray回收了,指针就无法根据地址去访问了,所以一定要创建一个变量(QList)去进行存储。

void MainWindow::read_nodes()
{
     //动态节点
    // 读取和解析多个节点的值
    UA_ReadValueId* valueIds=new UA_ReadValueId[2];
    QString add,temp;//
    char * sd;
    QList<QByteArray> list;//must,因为转char* 后存地址,如果临时变量传了地址但没有指向对象
    for(uint j=0;j<2;j++){
        add = QString("K%1").arg(10+j, 4, 10, QLatin1Char('0'));
        qDebug() << nodeValues[add];
        if(!nodeValues.contains(add)){
            qDebug()<<QString("地址[%1]不在节点列表中").arg(add);
        }
        list << nodeValues[add].toUtf8();
        sd=list[j].data();
        UA_ReadValueId_init(&valueIds[j]);
        valueIds[j].nodeId = UA_NODEID_STRING(2, sd);
        valueIds[j].attributeId = UA_ATTRIBUTEID_VALUE;
        //            qDebug() << "--11"<
        //            ba.clear();
        //            qDebug() << "--22"<
    }

    //1-------

 //固定节点
    //        UA_ReadValueId* valueIds=new UA_ReadValueId[2];
    //        UA_ReadValueId_init(&valueIds[0]);
    //        UA_ReadValueId_init(&valueIds[1]);
    //        QString add,temp;//
    //        char * sd;
            QString temp ="my.plc.mygroup.KS3";
    //        add = QString("K%1").arg(10, 4, 10, QLatin1Char('0'));
    //        qDebug() << nodeValues[add];
    //        temp =nodeValues[add];
    //        QByteArray ba =temp.toUtf8();
    //        sd=ba.data();
    //        valueIds[0].nodeId = UA_NODEID_STRING(2, sd);
    //        valueIds[0].attributeId = UA_ATTRIBUTEID_VALUE;

    //        //
            QString temp1 ="my.plc.mygroup.KS4";
    //        add = QString("K%1").arg(11, 4, 10, QLatin1Char('0'));
    //        qDebug() << nodeValues[add];
    //        temp =nodeValues[add];
    //        QByteArray ba1 =temp.toUtf8();
    //        sd=ba1.data();
    //        valueIds[1].nodeId = UA_NODEID_STRING(2, sd);
    //        valueIds[1].attributeId = UA_ATTRIBUTEID_VALUE;


    //2-----------
    UA_ReadRequest request;
    UA_ReadRequest_init(&request);
    request.nodesToReadSize = 2;
    request.nodesToRead = valueIds;
    UA_ReadResponse response;
    UA_ReadResponse_init(&response);
    response = UA_Client_Service_read(client, request);
    if(UA_STATUSCODE_GOOD == response.responseHeader.serviceResult) {
        for(size_t i = 0; i < response.resultsSize; ++i) {
            UA_Variant value = response.results[i].value;
            qDebug()<< "---" << value.data <<"----";
            //            if(value.type == &UA_TYPES[UA_TYPES_INT32]) {
            //            if(value.type == &UA_TYPES[UA_TYPES_INT16]) {
            //            if(value.type == &UA_TYPES[UA_TYPES_FLOAT]) {
            if(value.type == &UA_TYPES[UA_TYPES_INT16]){
                //            if(value.type == &UA_TYPES[UA_TYPES_STRING]){
                //                UA_Int32 intValue = *(UA_Int32 *)value.data;
                //                UA_Int16 intValue = *(UA_Int16 *)value.data;
                //                UA_Float intValue =*(UA_Float *)value.data;
                UA_Int16 intValue =*(UA_Int16 *)value.data;
                //                UA_String resp= *(UA_String*)value.data;
                //                QString intValue=plc_8toAscii_8(resp,ascii_dh);
                qDebug() << intValue;
            } else {
                qDebug()  << value.type;
                printf("Unsupported Data Type\n");
            }
        }
    }
    //
    printf("UA_ReadResponse_clear call\n");
    UA_ReadResponse_clear(&response);
}

写多节点(int16、int32、bool、string、float)

其实写单节点的UA_Client_writeValueAttribute底层就是用UA_Client_Service_write去实现,只不过前者进行了简单封装,所以实现单节点写入,该节封装方法也可用
注释内容为固定节点的使用,也可用。在编写动态节点的过程中,需要注意地址值->节点的转换,由于以for循环形式实现,一定要考虑临时变量回收的问题,由QString->char *,中间涉及到QByteArray,而QByteArray作为临时变量传地址给指针,如果QByteArray回收了,指针就无法根据地址去访问了,所以一定要创建一个变量(QList)去进行存储。

void MainWindow::write_nodes()
{
    //动态节点
    QList<qint16> temp ;
    temp <<1<<2;
    QVariant data ;
    data.setValue<QList<qint16>>(temp);
    //
    UA_WriteValue* valueIds=new UA_WriteValue[2];
    QList<qint16> back=data.value<QList<qint16>>();
    QString add;
    char * sd;
    QList<QByteArray> list;
    UA_Variant infoVar;
    for(uint j=0;j<2;j++){
        UA_WriteValue_init(&valueIds[j]);
        add = QString("K%1").arg(10+j, 4, 10, QLatin1Char('0'));
        qDebug() << nodeValues[add];
        if(!nodeValues.contains(add)){
            qDebug()<<QString("地址[%1]不在节点列表中").arg(add);
        }
        list << nodeValues[add].toUtf8();
        sd=list[j].data();

        valueIds[j].nodeId = UA_NODEID_STRING(2, sd);
        valueIds[j].attributeId = UA_ATTRIBUTEID_VALUE;

        UA_Variant_init(&infoVar);
        UA_Variant_setScalar(&infoVar, &back[j], &UA_TYPES[UA_TYPES_INT16]);
        valueIds[j].value.value = infoVar;
        valueIds[j].value.hasValue = true;

    }
//1-------------------
    //固定节点
//    UA_WriteValue valueIds[2];
//    UA_WriteValue_init(&valueIds[0]);
//    UA_WriteValue_init(&valueIds[1]);
//    //
//    valueIds[0].nodeId = UA_NODEID_STRING(2, (char *)"my.plc.mygroup.KS3");
//    valueIds[0].attributeId = UA_ATTRIBUTEID_VALUE;
//    //
//    valueIds[1].nodeId = UA_NODEID_STRING(2, (char *)"my.plc.mygroup.KS4");
//    valueIds[1].attributeId = UA_ATTRIBUTEID_VALUE;

//    UA_Variant infoVar;
//    UA_Int16 uint1Value = 101;
//    UA_Variant_init(&infoVar);
//    UA_Variant_setScalar(&infoVar, &uint1Value, &UA_TYPES[UA_TYPES_INT16]);
//    valueIds[0].value.value = infoVar;
//    valueIds[0].value.hasValue = true;

//    UA_Int16 uint2Value = -134;
//    UA_Variant_init(&infoVar);
//    UA_Variant_setScalar(&infoVar, &uint2Value, &UA_TYPES[UA_TYPES_INT16]);
//    valueIds[1].value.value = infoVar;
//    valueIds[1].value.hasValue = true;
//2-------------------
    //
    UA_WriteRequest wReq;
    UA_WriteRequest_init(&wReq);
    wReq.nodesToWrite = valueIds;
    wReq.nodesToWriteSize = 2;

    UA_WriteResponse wResp = UA_Client_Service_write(client, wReq);
    UA_StatusCode retval = wResp.responseHeader.serviceResult;
    if (retval == UA_STATUSCODE_GOOD) {
        if (wResp.resultsSize == 2){
            retval = wResp.results[0];
        }
        else{
            retval = UA_STATUSCODE_BADUNEXPECTEDERROR;
        printf("serviceResult analysis:%0x8d\n",retval);}

    }
    UA_WriteResponse_clear(&wResp);
}

KEPServerEX配置

基于编写过程中,涉及不同的数据类型,在地址设定中包括了Bxxxx、Sxxxx、Kxxxx,如图所示。服务器登录设置为匿名登录
【opcua】从编译文件到客户端的收发、断连、节点查询等实现_第2张图片

你可能感兴趣的:(qt,opcua,c++)