3. SECS-II消息结构精讲
SECS-II 消息是 EAP 与设备之间真正“说话”的载体。一条 SECS-II 消息由两大部分组成:固定 10 字节的消息头(Message Header)和紧随其后的消息体(Message Body)。消息头负责寻址、标识和路由,消息体则用数据项(Data Item)承载具体业务内容。理解这两部分如何拼接、每个字段如何取值,是排查通信异常、编写报文解析代码的基础。
一、10 字节消息头:字段布局与含义
消息头固定 10 字节,按顺序排列如下:
| 字节位置 | 字段 | 长度 | 说明 |
|---|---|---|---|
| 0 | 设备ID高字节 | 1 字节 | Upper Device ID |
| 1 | 设备ID低字节 | 1 字节 | Lower Device ID |
| 2 | W-bit | 1 bit | 等待位,置 1 表示需要回复 |
| 2 | Stream | 7 bit | 流号,0~127 |
| 3 | Function | 1 字节 | 功能码,0~255 |
| 4~5 | System Bytes | 2 字节 | 事务标识,用于请求/回复配对 |
| 6~9 | 消息体长度 | 4 字节 | 消息体字节数,不含消息头 |
注意第 2 字节是“W-bit + Stream”共用一个字节:最高位是 W-bit,低 7 位是 Stream。解析时通常写作 byte2 = (W_bit << 7) | Stream。例如 W-bit=1、Stream=1,则第 2 字节为 0x81;W-bit=0、Stream=6,则为 0x06。
二、系统字节(System Bytes):请求与回复的“身份证”
系统字节是 2 字节无符号整数,由发起方生成,用于把一次 Primary Message 和它对应的 Secondary Message 关联起来。设备收到请求后,回复消息必须原样带回相同的 System Bytes。EAP 侧通常维护一个递增计数器,每发一条新请求就加一,并记录“System Bytes → 等待中的事务”映射表;收到回复时用 System Bytes 查表,找到对应的业务上下文。
三、W-bit:决定“要不要回”
W-bit(Wait-bit)只有 1 位,却决定了通信的时序行为:
- W-bit = 1:接收方必须回复一条 Secondary Message,且回复消息的 W-bit 必须为 0。
- W-bit = 0:接收方不需要回复,属于单向通知。
例如 EAP 下发 S1F13(Establish Communications Request)时 W-bit=1,设备回 S1F14 时 W-bit=0。若 EAP 发出 W-bit=1 的请求后长时间收不到回复,就会触发 T3 超时。
四、Stream 与 Function:消息的“姓”和“名”
Stream(流)表示消息大类,Function(功能码)表示该类下的具体动作。二者合称 SxFy。约定俗成:奇数 Function 为请求,偶数 Function 为回复,即 Fy 与 F(y+1) 配对。例如:
- S1F1 / S1F2:Are You There / On Line Data
- S1F13 / S1F14:Establish Communications Request / Acknowledge
- S2F41 / S2F42:Host Command Send / Host Command Acknowledge
- S6F11 / S6F12:Event Report Send / Event Report Acknowledge
Stream 0 为保留;Stream 1 用于通信建立与状态查询;Stream 2 用于设备控制与参数;Stream 5 用于告警;Stream 6 用于事件报告;Stream 7 用于工艺程序管理。实际项目中,EAP 需要按设备能力集(Capability Set)决定支持哪些 SxFy。
五、消息长度与数据项编码规则
消息头第 6~9 字节是 4 字节大端整数,表示消息体的字节数。若消息体为空(例如 S1F2 的回复),该字段为 0。解析时必须先读长度,再按长度读取消息体,否则会粘包或截断。
消息体由若干数据项(Data Item)顺序排列组成。每个数据项由“格式字节 + 长度字节 + 数据”构成。格式字节高 6 位是格式码,低 2 位是长度字节数(Length Bytes)。常见格式码:
| 格式码 | 类型 | 示例 |
|---|---|---|
| 0o20 | Boolean | 0x25 0x01 0x01 |
| 0o40 | Binary | 0x41 0x02 0xAB 0xCD |
| 0o60 | ASCII | 0x61 0x05 "HELLO" |
| 0o70 | JIS-8 | 日文编码 |
| 0o90 | I8 / I1 | 有符号整数 |
| 0o A0 | I16 / I2 | 2 字节整数 |
| 0o B0 | I32 / I4 | 4 字节整数 |
| 0o F0 | F32 / F4 | 4 字节浮点 |
| 0o 00 | List | 0x01 0x02 表示 2 元素列表 |
以 ASCII 为例,格式字节 0x61:高 6 位 0o60 表示 ASCII,低 2 位 01 表示长度字段占 1 字节。若字符串为 "HELLO",则完整编码为 0x61 0x05 0x48 0x45 0x4C 0x4C 0x4F。若长度超过 255,则低 2 位改为 10(2 字节长度)或 11(3 字节长度)。
List 是唯一“长度表示元素个数”而非字节数的类型。例如 0x01 0x02 表示一个包含 2 个元素的 List,随后紧跟这 2 个元素的数据项编码。List 可以嵌套,形成树形结构,这是 SECS-II 表达复杂数据的核心机制。
六、完整报文示例
假设 EAP 向设备 ID=0 发送 S1F13,W-bit=1,System Bytes=0x0001,消息体为空。完整 10 字节消息头为:
00 00 81 0D 00 01 00 00 00 00
│ │ │ │ │ │
│ │ │ │ │ └─ 消息体长度 = 0
│ │ │ │ └─────── System Bytes = 0x0001
│ │ │ └────────── Function = 0x0D (13)
│ │ └───────────── W-bit=1, Stream=1 → 0x81
│ └──────────────── Device ID 低字节 = 0x00
└─────────────────── Device ID 高字节 = 0x00
设备回复 S1F14,W-bit=0,System Bytes 仍为 0x0001,消息体包含一个 2 元素的 List(如 COMMACK 和 MDLN)。解析时先读 10 字节头,确认 System Bytes 匹配,再按长度字段读取消息体,逐层解析 List 和其中的 ASCII 数据项。
掌握上述结构后,无论是用 Python 的 struct.unpack 手工解析,还是借助 secs4net、PySECS 等库,都能快速定位问题:先看 10 字节头是否合法,再核对 System Bytes 是否配对,最后按格式码逐层展开数据项。现场排障时,把原始十六进制报文按“头 10 字节 + 体”切开,往往一眼就能看出是长度字段写错、W-bit 漏置,还是 List 嵌套层级不对。