作为一种通信协议,文件传输是非常重要的。例如传输执行程序,图片,配置文件等等。文件传输的机制和类型在 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协议能够保证通信的安全性。