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 字节,其字段布局如下:
| 字节偏移 | 字段 | 长度 | 说明 |
|---|---|---|---|
| 0 | Device ID | 2 字节 | 设备标识,高字节在前 |
| 2 | Stream | 1 字节 | 功能大类 |
| 3 | Function | 1 字节 | 具体操作 |
| 4 | PType | 1 字节 | 协议类型,SECS-II 为 0 |
| 5 | SType | 1 字节 | 会话类型,SECS-II 为 0 |
| 6 | System Bytes | 4 字节 | 事务标识,用于匹配请求与回复 |
其中 System Bytes 是事务匹配的关键。主机发送请求时生成一个唯一的 4 字节值,设备回复时必须原样返回该值,这样主机才能将回复与请求对应起来。PType 和 SType 在标准 SECS-II 中均为 0,若出现非 0 值,通常表示使用了 HSMS 之外的协议扩展。
数据项格式:类型字节 + 长度字节 + 数据
消息体由若干数据项(Data Item)组成。每个数据项采用“类型字节 + 长度字节 + 数据”的三段式结构。类型字节的高 6 位表示格式码(Format Code),低 2 位表示长度字节的个数(Length Bytes)。长度字节的个数可以是 1、2 或 3,分别表示后续数据长度用 1、2 或 3 个字节表示。
常见格式码及其含义如下:
| 格式码 | 类型 | 说明 | 单元素长度 |
|---|---|---|---|
| 0o00 | L | 列表,元素为其他数据项 | — |
| 0o20 | B | 二进制,每元素 1 字节 | 1 字节 |
| 0o21 | Boolean | 布尔,每元素 1 字节 | 1 字节 |
| 0o22 | A | ASCII 字符串 | 1 字节 |
| 0o24 | I8 | 8 字节有符号整数 | 8 字节 |
| 0o25 | I1 | 1 字节有符号整数 | 1 字节 |
| 0o26 | I2 | 2 字节有符号整数 | 2 字节 |
| 0o28 | I4 | 4 字节有符号整数 | 4 字节 |
| 0o30 | F8 | 8 字节浮点数 | 8 字节 |
| 0o31 | F4 | 4 字节浮点数 | 4 字节 |
| 0o32 | U8 | 8 字节无符号整数 | 8 字节 |
| 0o34 | U1 | 1 字节无符号整数 | 1 字节 |
| 0o35 | U2 | 2 字节无符号整数 | 2 字节 |
| 0o36 | U4 | 4 字节无符号整数 | 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 类型,则递归解析其内部元素,直到所有字节消耗完毕。
在实际工程中,手工构造消息时最容易出错的地方是长度字节的计算。例如,一个包含 3 个 U2 元素的列表,其数据区长度为 3 × 2 = 6 字节,长度字节应写 0x06,而不是 0x03。另一个常见错误是混淆 L 类型的长度含义:L 类型的长度字节表示的是其内部所有子数据项的总字节数,而非元素个数。解析时若将 L 的长度误认为元素个数,会导致后续数据项错位。
掌握上述编码规则后,读者可以尝试手工构造一条 S2F13(Equipment Constant Request)消息,消息体中包含一个 L 类型列表,列表内嵌套多个 U4 类型的常量 ID。构造完成后,用十六进制编辑器逐字节核对,再编写一个简单的解析脚本验证解析结果是否与原始数据一致。这种“构造—核对—解析—验证”的闭环练习,是建立 SECS-II 编码直觉的最有效方式。