在进行opcua前,需要先下载opcua库,由于最终需要用于qt,因此有2个选择:用qt 自带的opcua 库,以及open62541库,博主是把2种都下载尝试了,考虑到封装的便捷性,最终采用open62541库。因此,本博客主要介绍open62541库的编译+使用,qt自带的库安装可参考大神们的资料:Qt添加QOpcUa类
一、从open62541 官网 下载最新版本的源文件Source Code(zip)
二、下载Visual Studio、Python、cmake-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 服务器KEPServerEX,内含客户端,但是如果需要验证加密连接功能,还需要下载第三方opcua客户端uaexpert-bin-win32;如果不考虑加密连接,为了方便查看所有节点,可下载第三方opcua客户端OPCUAClient。如图所示:
在编写过程中,推荐看所下载open62541-1.3 中所提供的官方示例open62541-1.3\examples\client.c,可满足简单的基本需求,如下功能实现是根据示例以及编译生成文件的基础上,进行封装扩展的功能
导入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
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.x1的my.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() ;
}
}
注释内容为官方提供示例,也可用。
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;
}
注释内容为官方提供示例,也可用。
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;
}
其实读单节点的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);
}
其实写单节点的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);
}
基于编写过程中,涉及不同的数据类型,在地址设定中包括了Bxxxx、Sxxxx、Kxxxx,如图所示。服务器登录设置为匿名登录。