一个客户端的BACnet用户使用基本读文件服务对某个文件进行一个“打开--读出--关闭”的操作。访问的文件可以是字节流,也可以是编号记录。以下是基本读文件的服务原语:
这是在基本读文件中定义的数据结构:
typedef struct BACnet_Atomic_Read_File_Data {
BACNET_OBJECT_TYPE object_type;
uint32_t object_instance;
BACNET_FILE_ACCESS_METHOD access;
union {
struct {
int32_t fileStartPosition;
uint32_t requestedOctetCount;
} stream;
struct {
int32_t fileStartRecord;
/* requested or returned record count */
uint32_t RecordCount;
} record;
} type;
BACNET_OCTET_STRING fileData;
bool endOfFile;
} BACNET_ATOMIC_READ_FILE_DATA;
在这个数据结构的定义中,包括了对象类型,对象实例,访问方法(access),一个联合体type,在联合体中包括了以下两个结构:stream 和 record。在VTS中,这两个结构代表了两种access:
回到arf_encode_apdu(...)函数:
int arf_encode_apdu(
uint8_t * apdu,
uint8_t invoke_id,
BACNET_ATOMIC_READ_FILE_DATA * data)
{
int apdu_len = 0; /* total length of the apdu, return value */
if (apdu) {
apdu[0] = PDU_TYPE_CONFIRMED_SERVICE_REQUEST;
apdu[1] = encode_max_segs_max_apdu(0, MAX_APDU);
apdu[2] = invoke_id;
apdu[3] = SERVICE_CONFIRMED_ATOMIC_READ_FILE; /* service choice */
apdu_len = 4;
apdu_len +=
encode_application_object_id(&apdu[apdu_len], data->object_type,
data->object_instance);
switch (data->access) {
case FILE_STREAM_ACCESS:
apdu_len += encode_opening_tag(&apdu[apdu_len], 0);
apdu_len += encode_application_signed(&apdu[apdu_len],
data->type.stream.fileStartPosition);
apdu_len +=
encode_application_unsigned(&apdu[apdu_len],
data->type.stream.requestedOctetCount);
apdu_len += encode_closing_tag(&apdu[apdu_len], 0);
break;
case FILE_RECORD_ACCESS:
apdu_len += encode_opening_tag(&apdu[apdu_len], 1);
apdu_len +=
encode_application_signed(&apdu[apdu_len],
data->type.record.fileStartRecord);
apdu_len +=
encode_application_unsigned(&apdu[apdu_len],
data->type.record.RecordCount);
apdu_len += encode_closing_tag(&apdu[apdu_len], 1);
break;
default:
break;
}
}
return apdu_len;
}
apdu[0]~apdu[3]存储了关于apdu报文的基本信息,具体分析看前面的文章。然后调用了encode_application_object_id(...),根据传入的对象类型 (object_type) 和对象实例 (object_instance) 进行编码,根据15.3.2中的服务过程,响应的BACnet用户检验了请求的合法性之后就会创建一个具有“ 对象说明符 ”参数所规定的类型的新对象的操作。具体编码过程可参照协议书的 20.2.14 小节。
data access 包括 FILE_STREAM_ACCESS 以及 FILE_RECORD_ACCESS 两个分类。当选择的是FILE_STREAM_ACCESS
的时候,表示文件流访问,包含了 ‘ 文件开始位置 ’ 参数和 ‘ 请求的字节数目 ’ 参数。
当选择的是FILE_STREAM_ACCESS 或者 FILE_RECORD_ACCESS 的时候,需要将它们的信息包含在opening_tag和closing_tag之间。以
FILE_STREAM_ACCESS 为例,在VTS中,它的格式如下:
可以看到,首先对opening_tag进行编码,然后开始对FILE_STREAM_ACCESS的内容进行编码,包括‘ 文件开始位置 ’ 和 ‘ 请求的字节数目 ‘,最后编码结束的时候需要进行closing_tag的编码。由于fileStartPosition 是一个整型类型,因此需要调用encode_application_signed(...)进行编码。由于encode_application_signed(....)之前的文章中没有提到过,这里就对这个函数做一个分析。
int encode_application_signed(
uint8_t * apdu,
int32_t value)
{
int len = 0; /* return value */
/* assumes that the tag only consumes 1 octet */
len = encode_bacnet_signed(&apdu[1], value);
len +=
encode_tag(&apdu[0], BACNET_APPLICATION_TAG_SIGNED_INT, false,
(uint32_t) len);
return len;
}
这个函数需要调用两个函数,encode_bacnet_signed(.....) 和encode_tag(.....),encode_tag(...)是对 fileStartPosition 中的application tag 进行编码,重点来看看encode_bacnet_signed(....)这个函数:
int encode_bacnet_signed(
uint8_t * apdu,
int32_t value)
{
int len = 0; /* return value */
if ((value >= -128) && (value < 128)) {
len = encode_signed8(&apdu[0], (int8_t) value);
} else if ((value >= -32768) && (value < 32768)) {
len = encode_signed16(&apdu[0], (int16_t) value);
} else if ((value > -8388608) && (value < 8388608)) {
len = encode_signed24(&apdu[0], value);
} else {
len = encode_signed32(&apdu[0], value);
}
return len;
}
根据20.2.5小节中的编码规则,有符号整型数值在内容字节中被编码成一个取值范围为-2^(8*L) 到 (2^(8*L) - 1)的,使用2的补码标记的二进制数。其中L 是用于对这个值编码的字节数。如果value的取值在[-128 , 128),L取值为1;当value取值在[-32768 , 32768),L取值为2;当value取值在[-8388608, 8388608),L取值为3;否则,L取值为4。
FILE_RECORD_ACCESS的编码与FILE_STREAM_ACCESS几乎相同,但是FILE_RECORD_ACCESS是一个无符号整型类型,因此在编码的时候需要调用encode_bacnet_unsigned(...)这个函数。
最后返回的是apdu的长度。