open62541学习:文件传输

           作为一种通信协议,文件传输是非常重要的。例如传输执行程序,图片,配置文件等等。文件传输的机制和类型在 OPC UA 中已经存在很长时间了。FileType (作为ObjectType)和ImageType长期以来一直是内置模型的一部分,并且也用于许多配套规范(例如;用于设备机器,视觉和数控的配套规范)。 

        自版本 1.5.2 起,文件传输已从第 5 部分中删除,并给出了自己的部分(UA 第 20 部分:文件传输)。

       在OPC UA DI中,该方法用于下载和上传软件。另一个成功的用例是Machinery 的ResultTransferType,它旨在为测量结果生成相应的报告并将其传输给客户端。

我计划使用Filetype 文件传输的方法来下载IEC61499 的功能块网络,功能块库。如此一来,将IEC61499 标准完全建立在opcua 的基础之上。而不采用其它自定义的协议。

文件类型(FileType)

FileType 是一个内置的对象类型,包含了一些特征变量和方法。

主要特征

文件大小(size)

定义文件的大小(以字节为单位)

允许写(Writable)

指示文件是否可写

用户允许写(UserWritable)

指示文件是否可写,同时考虑用户访问权限。

打开计数器(OpenCount)

指示文件上当前有效的文件句柄数。

最大长度(MaxBytestringlength)

上次打开时间(LastModifiedTime)

指示上次修改文件的时间。

建立文件实例的方法:

  UA_NodeId fileID;
    UA_ObjectAttributes fileAttr = UA_ObjectAttributes_default;
    fileAttr.displayName = UA_LOCALIZEDTEXT((char *)"en-US", (char *)"File");

    UA_Server_addObjectNode(server, UA_NODEID_NULL,
                            UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
                            UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
                            UA_QUALIFIEDNAME(1, (char *)"File"), UA_NODEID_NUMERIC(0, UA_NS0ID_FILETYPE),
                            fileAttr, NULL, &fileID);

不过,需要对Writable ,Useritable 设置为true。另一个重点是为其中的方法编写callback

FileType 中的方法

 FileType 中的方法包括:

  • 打开文件 Open 
  • 写文件 Write
  • 读文件 Read
  • 获取位置(Getposition)
  • 设置位置(SetPosition)
  • 关闭 Close

这几个方法与C语言中的文件操作基本是一一对应的。 

Open方法

输入参数:mode

输出参数:handle

其中:

mode 是文件打开的模式。

与C++ 的对应关系如下

OPC统一协议

C++

说明

文件已存在时的操作

文件不存在时的操作

附加

删除

0

0

0

1

“r”

打开文件进行读取

从头开始读

错误

0

1

1

0

“w”

创建一个用于写入的文件

销毁内容

创建新的

1

0

1

0

“A”

追加到文件以进行写入

写到最后

创建新的

0

0

1

1

“r+”

打开文件进行读/写

从头开始读

C/C++ 中的错误[2]

0

1

1

1

“w+”

创建一个文件用于读/写

销毁内容

创建新的

1

0

1

1

“一个+”

打开文件进行读/写

写到最后

创建新的

handle 是文件句柄,当打开文件后,返回一个文件句柄,后续的操作使用文件句柄。

写文件(Write)

输入参数:

1 文件句柄 handle

3 写入数据 ByteString 

读文件(Read)

输入参数:

1 文件句柄(Handle)

2 长度(Length)

输出参数:

1 读出数据(ByteString)

关闭文件

输入参数:文件句柄(handle)

获取位置(GetPosition)

         提供文件句柄的当前位置。

输入参数:文件句柄(Handle)

输出参数:位置(UInt64 )

设置位置(SetPosition)

         设置文件句柄的当前位置。如果调用读取或写入,则从该位置开始。如果位置高于文件大小,则位置设置为文件的末尾。

输入参数:

1 文件句柄(UInt32 )

2 位置(UInt64 )

传输效率

OPC UA 文件传输的效率,国外有一些测试,结果如下:

Protocol

Max. transfer time

Min. transfer time

Average transfer time

HTTP

36.96 [s]

17.01 [s]

22.27 [s]

FTP

34.73 [s]

19.41 [s]

22.99 [s]

OPC UA

23.88 [s]

18.21 [s]

20.53 [s]

相比执行,opcua 文件传输的效率是不错的。

open62541 文件传输

        open61541 是开源opcua 项目,该项目中并没有对FileType 操作的支持,但是FileType 是一个普通的对象类型,完全可以使用基本的程序实现FileType 对象的建立。而方法回调函数和文件操作需要程序需要额外的代码。可惜的是open61541 项目中并没有FileType 的Example,网络上也没有相对完整的例子。

代码(支持WriteFile)

#include <open62541/server.h>
#include <stdlib.h>
#include <stdio.h>
#include <iostream>
#include <unistd.h>
#include <fcntl.h>
/* Build Instructions (Linux)
 * - g++ server.cpp -lopen62541 -o server */

using namespace std;
char *UA_String2string(UA_String uaString)
{
  char *convert = (char *)UA_malloc(sizeof(char) * uaString.length + 1);
  memcpy(convert, uaString.data, uaString.length);
  convert[uaString.length] = '\0';
  return convert;
}
UA_StatusCode OpenFileCallback(UA_Server *server, const UA_NodeId *sessionId,
                           void *sessionContext, const UA_NodeId *methodId,
                           void *methodContext, const UA_NodeId *objectId,
                           void *objectContext, size_t inputSize,
                           const UA_Variant *input, size_t outputSize,
                           UA_Variant *output)
{
    cout << "Open File Callback" << endl;
    int FileMode=*(UA_Byte *)(input[0].data);
    cout<<"mode:"<<FileMode<<endl;
  uint32_t handle=open("File.txt",O_CREAT|O_RDWR,FileMode);
  cout<<"handel:"<<handle<<endl;
    UA_Variant_setScalarCopy(output, &handle, &UA_TYPES[UA_TYPES_UINT32]);
 //   output[0]=Handle;
    return UA_STATUSCODE_GOOD;
}
UA_StatusCode WriteFileCallback(UA_Server *server, const UA_NodeId *sessionId,
                           void *sessionContext, const UA_NodeId *methodId,
                           void *methodContext, const UA_NodeId *objectId,
                           void *objectContext, size_t inputSize,
                           const UA_Variant *input, size_t outputSize,
                           UA_Variant *output)
{
    cout << "Write File Callback" << endl;
    uint32_t handle=*(UA_UInt32 *)(input[0].data);
    cout<<"handle:"<<handle<<endl;
    UA_ByteString data=*(UA_ByteString *)(input[1].data);
  //  cout<<"Data:"<<UA_String2string(data)<<endl;
    write(handle,UA_String2string(data),data.length);
    return UA_STATUSCODE_GOOD;
}
UA_StatusCode CloseFileCallback(UA_Server *server, const UA_NodeId *sessionId,
                           void *sessionContext, const UA_NodeId *methodId,
                           void *methodContext, const UA_NodeId *objectId,
                           void *objectContext, size_t inputSize,
                           const UA_Variant *input, size_t outputSize,
                           UA_Variant *output)
{
    cout << "Close File Callback" << endl;
      uint32_t handle=*(UA_UInt32 *)(input[0].data);
      close(handle);
    return UA_STATUSCODE_GOOD;
}
int getNodeIdByPath(UA_Server *server,
                    UA_NodeId parentNode,
                    const int relativePathCnt,
                    const UA_QualifiedName targetNameArr[],
                    UA_NodeId *result)
{
    int ret = 0;

    UA_BrowsePathResult bpr = UA_Server_browseSimplifiedBrowsePath(server,
                                                                   parentNode, relativePathCnt, targetNameArr);

    if (bpr.statusCode != UA_STATUSCODE_GOOD || bpr.targetsSize < 1)
    {
        // printf("error: %s\n", UA_StatusCode_name(bpr.statusCode));
        ret = -1;
    }
    else
    {
        UA_NodeId_copy(&bpr.targets[0].targetId.nodeId, result);
    }

    UA_BrowsePathResult_clear(&bpr);
    return ret;
}
int main()
{
    UA_Server *server = UA_Server_new();

    // add a variable node to the adresspace
    UA_VariableAttributes attr = UA_VariableAttributes_default;
    UA_Int32 myInteger = 42;
    UA_Variant_setScalarCopy(&attr.value, &myInteger, &UA_TYPES[UA_TYPES_INT32]);
    attr.description = UA_LOCALIZEDTEXT_ALLOC("en-US", "the answer");
    attr.displayName = UA_LOCALIZEDTEXT_ALLOC("en-US", "the answer");
    UA_NodeId myIntegerNodeId = UA_NODEID_STRING_ALLOC(1, "the.answer");
    UA_QualifiedName myIntegerName = UA_QUALIFIEDNAME_ALLOC(1, "the answer");
    UA_NodeId parentNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER);
    UA_NodeId parentReferenceNodeId = UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES);
    UA_Server_addVariableNode(server, myIntegerNodeId, parentNodeId,
                              parentReferenceNodeId, myIntegerName,
                              UA_NODEID_NULL, attr, NULL, NULL);
    // Add FileType
    UA_NodeId fileID;
    UA_ObjectAttributes fileAttr = UA_ObjectAttributes_default;
    fileAttr.displayName = UA_LOCALIZEDTEXT((char *)"en-US", (char *)"File");

    UA_Server_addObjectNode(server, UA_NODEID_NULL,
                            UA_NODEID_NUMERIC(0, UA_NS0ID_OBJECTSFOLDER),
                            UA_NODEID_NUMERIC(0, UA_NS0ID_ORGANIZES),
                            UA_QUALIFIEDNAME(1, (char *)"File"), UA_NODEID_NUMERIC(0, UA_NS0ID_FILETYPE),
                            fileAttr, NULL, &fileID);
    //set Writeable true
    UA_Boolean enabled = true;
    UA_Variant value;
    UA_Variant_setScalar(&value, &enabled, &UA_TYPES[UA_TYPES_BOOLEAN]);
     UA_QualifiedName targetNameArr[1] = {
        UA_QUALIFIEDNAME(0, (char *)"Writable")};
    UA_NodeId WritableNodeId;
    int ret = getNodeIdByPath(server, fileID,
                              1, targetNameArr, &WritableNodeId);
     UA_Server_writeValue(server, WritableNodeId, value);  
     //set UserWritable true
        UA_QualifiedName targetNameArrU[1] = {
        UA_QUALIFIEDNAME(0, (char *)"UserWritable")};
    UA_NodeId UserWritableNodeId;
    ret = getNodeIdByPath(server, fileID,
                              1, targetNameArrU, &UserWritableNodeId);
     UA_Server_writeValue(server, UserWritableNodeId, value);                          
    //Open
    UA_QualifiedName targetNameArrA[1] = {
        UA_QUALIFIEDNAME(0, (char *)"Open")};
    UA_NodeId OpenFileNodeId;
    ret = getNodeIdByPath(server, fileID,
                              1, targetNameArrA, &OpenFileNodeId);
    cout << "OpenFileNodeId:" << OpenFileNodeId.identifier.numeric << endl;
    UA_Server_setMethodNode_callback(server, OpenFileNodeId, OpenFileCallback);
    //Write
      UA_QualifiedName targetNameArrB[1] = {
        UA_QUALIFIEDNAME(0, (char *)"Write")};
    UA_NodeId WriteFileNodeId;
     ret = getNodeIdByPath(server, fileID,
                              1, targetNameArrB, &WriteFileNodeId);
    cout << "WriteFileNodeId:" << WriteFileNodeId.identifier.numeric << endl;
    UA_Server_setMethodNode_callback(server, WriteFileNodeId, WriteFileCallback);
    //Close File
     
      UA_QualifiedName targetNameArrC[1] = {
        UA_QUALIFIEDNAME(0, (char *)"Close")};
    UA_NodeId CloseFileNodeId;
     ret = getNodeIdByPath(server, fileID,
                              1, targetNameArrC, &CloseFileNodeId);
    cout << "CloseFileNodeId:" << CloseFileNodeId.identifier.numeric << endl;
    UA_Server_setMethodNode_callback(server, CloseFileNodeId, CloseFileCallback);
    //
    /* allocations on the heap need to be freed */
    UA_VariableAttributes_clear(&attr);
    UA_NodeId_clear(&myIntegerNodeId);
    UA_QualifiedName_clear(&myIntegerName);

    UA_StatusCode retval = UA_Server_runUntilInterrupt(server);

    UA_Server_delete(server);
    return retval == UA_STATUSCODE_GOOD ? EXIT_SUCCESS : EXIT_FAILURE;
}

 代码有一些调试的痕迹,但是在ubuntu 上是能够运行的。

使用uaExpert 测试

在FileType 实例击右键

点击 Write from local file,选择本地文件。

结束语

        文件传输对于自动化控制器十分重要,在传统的控制器中,普遍采用专有协议,或者FTP,TCP/IP ,HTTP等非语义通信协议实现文件下载。造成了IDE 与下载协议紧耦合,阻碍了系统的开放性。OPCUA 定义了文件类型信息模型。为文件传输定义了规范。任何一个OPCUA 的Client 都能够实现文件的下载和上传。笔者主张控制器尽可能采用单一协议。开发工具与下载文件协议解构。实现完全开放,同时,使用OPCUA协议能够保证通信的安全性。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值