作为一种通信协议,文件传输是非常重要的。例如传输执行程序,图片,配置文件等等。文件传输的机制和类型在 OPC UA 中已经存在很长时间了。FileType (作为ObjectType)和ImageType长期以来一直是内置模型的一部分,并且也用于许多配套规范(例如;用于设备的 OPC UA、用于机器视觉的 OPC UA、OPC UA )用于数控)。
自版本 1.5.2 起,文件传输已从第 5 部分中删除,并给出了自己的部分(UA 第 20 部分:文件传输)。
在OPC UA DI中,该方法用于下载和上传软件。另一个成功的用例是Machinery 的ResultTransferType,它旨在为测量结果生成相应的报告并将其传输给客户端。
我计划使用Filetype 文件传输的方法来下载IEC61499 的功能块网络,功能块库。如此一来,将IEC61499 标准完全建立在opcua 的基础之上。而不采用其它自定义的协议。
文件类型(FileType)
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);
不过,需要对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 是文件句柄,当打开文件后,返回一个文件句柄,后续的操作使用文件句柄。
写文件
输入参数:
1 文件句柄 handle
3 写入数据 ByteString
关闭文件
输入参数:文件句柄(handle)
传输效率
国外有一些测试,结果如下:
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
结束语
希望能够帮到你。