2. SECS-II消息结构与数据项编码实战

SECS-II 消息是设备与主机之间交换数据的实际载体。一条完整的 SECS-II 消息由三部分构成:消息头(Message Header)、消息体(Message Body)和消息长度(Message Length)。理解这三者的编码规则,是手工构造和解析 SECS-II 消息的基础。本节将围绕 Stream/Function 编号、消息头结构、数据项格式以及长度计算展开,并通过一个典型消息的构造与解析示例,帮助读者建立完整的编码直觉。

Stream 与 Function:消息的“地址”

SECS-II 用 Stream(S)和 Function(F)两个数字来标识消息类型。Stream 表示消息所属的功能大类,Function 表示该大类下的具体操作。例如 S1F1 是“Are You There”请求,S1F2 是对应的回复;S2F13 是“Equipment Constant Request”,S2F14 是回复。S 和 F 各占一个字节,取值范围通常为 0~127,但实际使用中 S 多为 1~64,F 多为 0~255。S 和 F 的组合决定了接收方如何解释消息体中的数据项。

在编码时,S 和 F 分别写入消息头的第 2 和第 3 字节。需要注意的是,S 和 F 本身不携带数据类型信息,它们只是“路由标签”,真正的数据语义由消息体中的数据项格式决定。

消息头结构:10 字节的固定框架

SECS-II 消息头固定为 10 字节,其字段布局如下:

字节偏移字段长度说明
0Device ID2 字节设备标识,高字节在前
2Stream1 字节功能大类
3Function1 字节具体操作
4PType1 字节协议类型,SECS-II 为 0
5SType1 字节会话类型,SECS-II 为 0
6System Bytes4 字节事务标识,用于匹配请求与回复

其中 System Bytes 是事务匹配的关键。主机发送请求时生成一个唯一的 4 字节值,设备回复时必须原样返回该值,这样主机才能将回复与请求对应起来。PType 和 SType 在标准 SECS-II 中均为 0,若出现非 0 值,通常表示使用了 HSMS 之外的协议扩展。

数据项格式:类型字节 + 长度字节 + 数据

消息体由若干数据项(Data Item)组成。每个数据项采用“类型字节 + 长度字节 + 数据”的三段式结构。类型字节的高 6 位表示格式码(Format Code),低 2 位表示长度字节的个数(Length Bytes)。长度字节的个数可以是 1、2 或 3,分别表示后续数据长度用 1、2 或 3 个字节表示。

常见格式码及其含义如下:

格式码类型说明单元素长度
0o00L列表,元素为其他数据项—
0o20B二进制,每元素 1 字节1 字节
0o21Boolean布尔,每元素 1 字节1 字节
0o22AASCII 字符串1 字节
0o24I88 字节有符号整数8 字节
0o25I11 字节有符号整数1 字节
0o26I22 字节有符号整数2 字节
0o28I44 字节有符号整数4 字节
0o30F88 字节浮点数8 字节
0o31F44 字节浮点数4 字节
0o32U88 字节无符号整数8 字节
0o34U11 字节无符号整数1 字节
0o35U22 字节无符号整数2 字节
0o36U44 字节无符号整数4 字节

以 U4 为例,格式码为 0o36,若长度字节个数为 1,则类型字节为 0o36 | 0o01 = 0o37,即十六进制 0x37。若数据长度为 4 字节(一个 U4 元素),则长度字节为 0x04,后续 4 字节为实际数值。对于多元素数据项,长度字节表示的是所有元素的总字节数,而非元素个数。例如 3 个 U4 元素,总长度为 12 字节,长度字节为 0x0C。

消息长度计算与完整消息构造

消息长度字段位于消息头之前,占 4 字节,表示消息头与消息体的总字节数。计算时,先确定消息体长度,再加上固定的 10 字节消息头。消息体长度等于所有数据项的长度之和,每个数据项的长度为其类型字节、长度字节和数据字节的总和。

下面以 S1F1(Are You There)为例,手工构造一条完整消息。S1F1 的消息体为空,因此消息体长度为 0,消息长度字段为 10(仅消息头)。假设 Device ID 为 0x0001,System Bytes 为 0x00000001,则完整消息的十六进制表示如下:

00 00 00 0A   // 消息长度 = 10
00 01         // Device ID = 1
01            // Stream = 1
01            // Function = 1
00            // PType = 0
00            // SType = 0
00 00 00 01   // System Bytes = 1

再以 S1F2 回复为例,假设回复中携带一个 A 类型数据项,内容为 "OK"。A 类型的格式码为 0o22,长度字节个数为 1,类型字节为 0o22 | 0o01 = 0o23,即 0x23。数据 "OK" 占 2 字节,长度字节为 0x02。消息体总长度为 1(类型字节)+ 1(长度字节)+ 2(数据)= 4 字节。消息长度字段为 10 + 4 = 14,即 0x0000000E。完整消息如下:

00 00 00 0E   // 消息长度 = 14
00 01         // Device ID = 1
01            // Stream = 1
02            // Function = 2
00            // PType = 0
00            // SType = 0
00 00 00 01   // System Bytes = 1
23 02 4F 4B   // A 类型,长度 2,数据 "OK"

解析时,先读取前 4 字节得到消息长度,再读取 10 字节消息头,提取 S、F 和 System Bytes,最后根据剩余长度逐项解析数据项。每解析一个数据项,先读类型字节,提取格式码和长度字节个数,再读取相应数量的长度字节,最后根据格式码和长度读取数据。若数据项为 L 类型,则递归解析其内部元素,直到所有字节消耗完毕。

SECS-II 消息结构与数据项编码 消息长度 4 字节 消息头(10 字节) Device ID(2) + S(1) + F(1) + PType(1) + SType(1) + System Bytes(4) 消息体(数据项序列) 每个数据项 = 类型字节 + 长度字节 + 数据 数据项编码结构 类型字节 高6位格式码 + 低2位长度字节数 长度字节 1~3 字节,表示数据总长度 数据区 按格式码解释,多元素时总长度 = 元素数 × 单元素长度 常用格式码与编码示例 A (0o22) ASCII 字符串 0x23 0x02 "OK" U4 (0o36) 4 字节无符号整数 0x37 0x04 00 00 00 64 L (0o00) 列表,嵌套数据项 0x01 0x0A ... Boolean (0o21) 1 字节布尔值 0x25 0x01 0x01 F4 (0o31) 4 字节浮点 0x33 0x04 ...

在实际工程中,手工构造消息时最容易出错的地方是长度字节的计算。例如,一个包含 3 个 U2 元素的列表,其数据区长度为 3 × 2 = 6 字节,长度字节应写 0x06,而不是 0x03。另一个常见错误是混淆 L 类型的长度含义:L 类型的长度字节表示的是其内部所有子数据项的总字节数,而非元素个数。解析时若将 L 的长度误认为元素个数,会导致后续数据项错位。

实践建议:在编写 SECS-II 消息解析器时,建议先实现一个通用的数据项解析函数,输入为字节流和偏移量,输出为解析后的数据项对象和新的偏移量。该函数根据类型字节递归处理 L 类型,非 L 类型则按格式码和长度直接读取。这样可以将消息头解析与消息体解析解耦,便于单独测试。

掌握上述编码规则后,读者可以尝试手工构造一条 S2F13(Equipment Constant Request)消息,消息体中包含一个 L 类型列表,列表内嵌套多个 U4 类型的常量 ID。构造完成后,用十六进制编辑器逐字节核对,再编写一个简单的解析脚本验证解析结果是否与原始数据一致。这种“构造—核对—解析—验证”的闭环练习,是建立 SECS-II 编码直觉的最有效方式。