4. SECS-II消息编码与解码实战

SECS-II 是 Host 与设备之间真正承载业务语义的层。它规定了消息由 SxFy(Stream 与 Function)标识,消息体由若干数据项(Data Item)按顺序组成,数据项可以是标量,也可以是列表,列表内部还能继续嵌套列表。EAP 联机调试中大量问题并非出在 HSMS 连接,而是出在“消息体结构理解错了”:字段顺序错、类型错、列表层级错,都会导致设备回 S9F7 或直接不响应。因此,掌握 SECS-II 的编码与解码,是联机开发的核心基本功。

一、三种表示形式:SML、XML 与二进制

同一条 SECS-II 消息有三种常见表达方式。二进制是线上真实传输的格式,由长度字节、格式字节和数据字节组成;SML(SECS Message Language)是面向人的文本表示,常用于日志、测试脚本和文档;XML 表示则便于与上层 MES/系统集成。三者可以互相转换,理解它们的对应关系是解码的第一步。

以一条常见的 S1F13(Establish Communications Request)为例,其 SML 表示如下:

S1F13 W
<L [2]
  <A [6] 'EQUIP1'
  <A [4] 'HOST'>
.

其中 W 表示需要回复(Wait),<L [2] 表示一个含 2 个元素的列表,<A [6] 表示长度为 6 的 ASCII 字符串。对应的 XML 表示则把结构显式标签化:

<SECSMessage s="1" f="13" replyBit="true">
  <List count="2">
    <ASCII length="6">EQUIP1</ASCII>
    <ASCII length="4">HOST</ASCII>
  </List>
</SECSMessage>

可以看到,SML 用尖括号和方括号紧凑表达,XML 用标签和属性表达,但两者描述的是同一棵树。解码的本质,就是把二进制字节流还原成这棵树;编码则是把这棵树序列化成字节流。

SECS-II 消息三种表示与编解码关系 二进制(线上传输) 长度字节 | 格式字节 | 数据 01 02 41 06 45 51 ... 格式字节含类型+长度位数 列表用 0x00 表示元素个数 SML(人可读文本) <L [2] <A [6] 'EQUIP1' <A [4] 'HOST'> 用于日志/脚本/文档 XML(系统集成) <List count="2"> <ASCII>EQUIP1</ASCII> <ASCII>HOST</ASCII> 便于与 MES 对接 解码 转换 SECS-II 常用数据项类型(格式码) L 列表 0x00 可嵌套 A ASCII 0x40 字符串 U 无符号整型 U1/U2/U4 B 二进制 0x20 位串 Boolean 0x11 列表嵌套示例:<L [2] <A 'EQ'> <L [2] <U4 100> <U4 200>>> 解码时按“类型码 + 长度 + 值”逐层递归还原树结构

二、数据项类型与列表嵌套

SECS-II 的数据项类型由格式码决定,常见的有:L(列表,0x00)、B(二进制,0x20)、Boolean(0x11)、A(ASCII,0x40)、J(有符号整型)、U(无符号整型,U1/U2/U4/U8)、F(浮点,F4/F8)。每个数据项编码时先写格式字节,再写长度字节,最后写数据。列表比较特殊:它的“长度”表示的是元素个数,而不是字节数,且列表本身不携带元素类型信息,必须靠双方约定的消息结构来解析。

列表嵌套是解析中最容易出错的地方。例如 S2F41(Host Command)的典型结构是:

S2F41 W
<L [2]
  <A [8] 'STARTCMD'
  <L [1]
    <L [2]
      <A [4] 'PORT'
      <A [2] '01'>>>
.

这里外层列表有 2 个元素:命令名和一个参数列表;参数列表里每个参数又是一个二元列表(参数名 + 参数值)。解码时必须按“外层 → 参数列表 → 单个参数”三层递归展开,任何一层计数错误都会导致后续字段全部错位。

调试建议:在 EAP 中打印消息时,优先输出 SML 格式而非原始十六进制。SML 的缩进能直观反映列表层级,一眼就能看出嵌套是否正确。

三、常见 SxFy 消息的解析与构造

联机中最常打交道的消息包括:S1F13/S1F14(通信建立)、S1F1/S1F2(Are You There)、S2F41/S2F42(Host Command)、S2F49/S2F50(Enhanced Remote Command)、S6F11/S6F12(Event Report)、S5F1/S5F2(Alarm Report)。以 S6F11 事件报告为例,其结构通常为:

S6F11 W
<L [3]
  <U4 [1] 1001          // CEID 事件ID
  <L [1]                // 报告列表
    <L [2]
      <U4 [1] 2001      // RPTID 报告ID
      <L [2]            // 变量列表
        <A [4] 'TEMP'
        <F4 [1] 25.5>>>
  <L [0]>               // 可选数据,空列表
.

构造这条消息时,需要依次写入:CEID、报告列表、每个报告内的 RPTID 和变量列表。变量值类型必须与设备定义一致,例如温度用 F4,若误写成 A,设备可能直接丢弃该报告。解析时则反向操作:先读 CEID,再遍历报告列表,逐层取出 RPTID 和变量名值对。

常见坑:U4 与 U1 混用。CEID 通常用 U4,但部分设备用 U2 甚至 U1。若 Host 按 U4 解析而设备发的是 U2,长度字节会差 2 字节,导致整个消息体错位。联机前务必用 SML 日志核对设备实际发送的类型码。

四、编解码的实操要点

在代码实现上,建议把编解码拆成两个独立函数:encode(item) 负责把内存中的树结构序列化为字节流,decode(bytes) 负责把字节流还原为树。解码时维护一个游标,每读一个数据项就前进相应字节数;遇到列表则递归调用自身,直到读完该列表声明的元素个数。这样无论嵌套多深,逻辑都保持一致。

验证编解码是否正确,最直接的方法是用 SML 做“往返测试”:把一条 SML 消息编码成二进制,再解码回 SML,比较两者是否一致。若一致,说明编解码对称;若不一致,通常是长度字节或列表计数写错。实际联机时,再结合设备返回的 SxFy 和 S9F7(Illegal Data)判断是结构问题还是权限问题。

掌握 SECS-II 的编码与解码,本质上就是掌握“树结构 + 类型码 + 长度”这三件事。把 SML 当作调试语言,把 XML 当作集成语言,把二进制当作传输语言,三者互转熟练之后,绝大多数联机消息问题都能快速定位。