3. SECS‑II 消息结构详解:Stream、Function、Item 与编码规则
SECS‑II 是 SECS/GEM 协议栈中定义消息内容与格式的层。它不关心消息如何传输(那是 HSMS 或 RS‑232 的工作),只规定:消息由什么组成、如何描述数据、如何编码成字节流。理解 SECS‑II 是开发设备端通信模块、编写消息解析器、调试通信日志的必备基础。
学习目标:掌握 Stream/Function 的语义与分类、Item 的 6 种基本数据类型、消息的嵌套结构与编码规则,能够手动解析或构造一条 SECS‑II 消息。
3.1 Stream 与 Function:消息的“地址”
每一条 SECS‑II 消息都由一个 Stream (S) 和一个 Function (F) 唯一标识。Stream 表示消息的类别,Function 表示该类下的具体操作。格式为 S<n>F<m>,例如 S1F13、S6F11。
- Stream 编号:0–127,常用 0–63。偶数 Stream 通常用于设备端向主机发送的消息(设备主动上报),奇数 Stream 用于主机向设备发送的消息(主机请求/控制)。
- Function 编号:0–255,其中 0–63 为标准功能,64–127 为设备自定义,128–255 保留。
- W bit:Function 编号最高位(bit 7)为 1 时表示该消息需要回复(Wait bit),例如
S1F13的 Function 13 二进制为00001101,W bit=0;S1F14的 Function 14 二进制为10001110,W bit=1,表示主机发送 S1F13 后设备必须回复 S1F14。
常见 Stream 分类速查:
| Stream | 类别 | 典型消息 |
|---|---|---|
| S1 | 设备状态与通信控制 | S1F13 (通信建立请求), S1F14 (通信建立确认) |
| S2 | 设备诊断与参数管理 | S2F41 (主机发送数据), S2F42 (设备确认) |
| S5 | 报警管理 | S5F1 (报警上报), S5F2 (报警确认) |
| S6 | 数据收集与事件通知 | S6F11 (事件报告), S6F12 (事件报告确认) |
| S7 | 配方管理 | S7F1 (配方请求), S7F2 (配方数据) |
| S10 | 终端服务 | S10F3 (终端显示消息), S10F4 (终端显示确认) |
3.2 Item 数据类型:消息的“积木块”
SECS‑II 消息体由若干个 Item 组成。每个 Item 包含一个 数据项头 和 数据内容。Item 可以嵌套(List 类型),形成树形结构。标准定义了 6 种基本数据类型:
| 类型码 | 缩写 | 名称 | 字节长度 | 说明 |
|---|---|---|---|---|
| 0 | L | List | 可变 | 子 Item 的容器,长度字段表示子 Item 个数 |
| 1 | B | Binary | 1–n 字节 | 原始字节数组,无符号 |
| 2 | B | Boolean | 1 字节 | 0x00=False, 0x01=True |
| 3 | A | ASCII | 1–n 字节 | 可打印 ASCII 字符 (0x20–0x7E) |
| 4 | I1 | Signed Integer 1‑byte | 1 字节 | –128 到 127 |
| 5 | I2 | Signed Integer 2‑byte | 2 字节 | –32,768 到 32,767 (大端) |
| 6 | I4 | Signed Integer 4‑byte | 4 字节 | –2³¹ 到 2³¹–1 (大端) |
| 7 | F4 | Float 4‑byte | 4 字节 | IEEE 754 单精度 (大端) |
| 8 | F8 | Float 8‑byte | 8 字节 | IEEE 754 双精度 (大端) |
| 9 | U1 | Unsigned Integer 1‑byte | 1 字节 | 0 到 255 |
| 10 | U2 | Unsigned Integer 2‑byte | 2 字节 | 0 到 65,535 (大端) |
| 11 | U4 | Unsigned Integer 4‑byte | 4 字节 | 0 到 4,294,967,295 (大端) |
注意:实际工程中 L (List) 是最常用的容器类型,用于构建嵌套结构。例如一条 S6F11 事件报告消息,最外层是 List,内部包含多个子 List 和 A/I4 等类型。解析时必须递归处理。
3.3 消息格式与编码规则
SECS‑II 消息采用 TLV (Type-Length-Value) 编码,但这里的 Type 是 格式字节 (Format Byte),同时携带类型和长度信息。编码规则如下:
- 格式字节 (1 字节):高 4 位表示数据类型(0–11),低 4 位表示长度字节数(0–3)。长度字节数表示后续长度字段占几个字节。
- 长度字段 (0–3 字节):大端无符号整数,表示 Value 部分的字节数。对于 List 类型,长度表示子 Item 的个数。
- Value 字段:实际数据。对于 List,Value 是连续的多个子 Item 的编码。
编码示例:一个 ASCII 字符串 "OK"
格式字节: 0x41 (类型 A=3, 长度字节数=1)
长度字段: 0x02 (2 字节)
Value: 0x4F 0x4B ("OK")
完整编码: 41 02 4F 4B
3.4 嵌套结构与消息树
由于 List 可以包含任意类型的 Item(包括其他 List),SECS‑II 消息天然形成一棵 消息树。解析时需递归遍历。以下是一个典型的 S6F11 事件报告消息结构:
消息: S6F11 (事件报告)
最外层 List (L, 子项数=3)
├── List (L, 子项数=2)
│ ├── ASCII (A) "CEID" → 事件 ID
│ └── I4 (4 字节) 1001 → 事件编号
├── List (L, 子项数=2)
│ ├── ASCII (A) "DATAID" → 数据 ID
│ └── I4 (4 字节) 5 → 数据编号
└── List (L, 子项数=1)
└── List (L, 子项数=2)
├── ASCII (A) "SVID" → 状态变量 ID
└── I4 (4 字节) 200 → 变量值
工程建议:在编写解析器时,不要假设消息的层级深度。始终使用递归或栈来解析 List 嵌套。调试时,将消息打印为树形结构(如上)能快速定位问题。
3.5 常见编码陷阱与避坑
| 陷阱 | 现象 | 解决方案 |
|---|---|---|
| 长度字节数错误 | 解析出的 Value 长度异常,导致后续 Item 错位 | 严格按格式字节低 4 位读取长度字段,不要假设固定长度 |
| 大端/小端混淆 | 整数/浮点数值错误 | SECS‑II 固定大端,使用 ntohl 或手动移位 |
| List 长度表示子项数而非字节数 | 将 List 长度当作字节数解析,导致递归崩溃 | List 的长度字段永远表示子 Item 个数,不是字节数 |
| ASCII 字符串未以 NUL 结尾 | 字符串末尾出现乱码 | SECS‑II 的 A 类型不要求 NUL 结尾,长度字段决定有效字符数 |
| Boolean 类型误用 | 将 0x00/0x01 以外的值当作布尔 | 严格检查:0x00=False, 0x01=True,其他值应视为非法 |
3.6 手动解析示例
假设收到以下字节流(十六进制):
41 02 4F 4B
解析步骤:
- 读取第 1 字节
0x41:高 4 位 = 3 (ASCII),低 4 位 = 1 (长度字段占 1 字节)。 - 读取第 2 字节
0x02:Value 长度为 2 字节。 - 读取第 3–4 字节
0x4F 0x4B:ASCII 解码为 "OK"。
再考虑一个嵌套 List 示例:
00 02 (格式字节 0x00: List, 长度字节数=0? 实际低4位=0 表示长度固定? 不, 低4位=0 表示长度字段占0字节, 但 List 必须有长度! 这里修正: 正确 List 编码应为 0x01 或 0x02 等)
正确示例:
0x01 0x02 (格式字节 0x01: List, 长度字节数=1; 长度字段 0x02: 2 个子 Item)
子 Item 1: 0x41 0x02 0x4F 0x4B (ASCII "OK")
子 Item 2: 0x71 0x01 0x2A (U1 类型, 长度=1, Value=0x2A=42)
完整编码对照:
原始字节: 01 02 41 02 4F 4B 71 01 2A
解析结果:
List (2 个子项)
├── ASCII "OK"
└── U1 42
3.7 本章小结
- SECS‑II 消息由 Stream/Function 标识,W bit 控制是否需要回复。
- 消息体由 Item 组成,Item 类型包括 L、B、A、I1–I4、F4/F8、U1–U4 等。
- 编码采用 TLV 格式:格式字节(类型+长度字节数)+ 长度字段 + Value。
- List 嵌套 形成消息树,解析时必须递归。
- 工程中注意大端字节序、List 长度语义、ASCII 无 NUL 结尾等细节。