2. SECS-II消息结构深度剖析

在SECS/GEM通信中,一条SECS-II消息并不是“一串数据”那么简单。它由消息头(Message Header)和消息体(Message Body,即数据区)两大部分组成。消息头固定10字节,承载了设备与主机之间“谁发给谁、属于哪个事务、要不要回复、消息有多长”等关键控制信息;数据区则按照SECS-II数据项(Item)的规则编码,承载真正的业务内容。理解这两部分的字节级结构,是后续解析日志、构造指令、排查通信异常的基础。

一条完整的SECS-II消息 = 10字节消息头 + 0~N字节数据区。消息头中的长度字段只描述数据区的字节数,不包含消息头自身。

一、消息头的10字节布局

消息头是定长的10字节结构,按顺序依次为:设备ID(2字节)、W-bit与流码(1字节)、功能码(1字节)、事务ID(4字节)、系统字节(1字节)、数据区长度(1字节)。其中“W-bit与流码”共用一个字节,高1位是W-bit,低7位是Stream;“系统字节”高1位是结束位(End-bit),低7位是系统字节值。下面这张结构图把每个字段的位置、位宽和含义一次看清。

SECS-II 消息头 10 字节结构(字节序:大端) Byte 0 Byte 1 Byte 2 Byte 3 Byte 4 Byte 5 Byte 6 Byte 7 Byte 8 Byte 9 设备ID Device ID (2B) W-bit + Stream 1B Function 1B 事务ID Transaction ID 4B 系统字节 1B 数据区长度 Length (1B) bit7 = W-bit bit6~0 = Stream (0~127) bit7 = End-bit bit6~0 = 系统字节值 数据区 Message Body(0 ~ N 字节) 由若干 SECS-II Item 组成:Item头(格式码+长度字节数)+ 长度 + 数据 Length 字段描述数据区字节数 示例:S1F13(主机请求建立通信)→ 设备ID=0x0000,Stream=1,Function=13,W-bit=1,事务ID=0x00000001,系统字节=0x00,长度=0x00 字节序列:00 00 81 0D 00 00 00 01 00 00

二、设备ID与事务ID:谁在说话、说的是哪件事

设备ID(Device ID)占2字节,用于标识消息的发送方或接收方设备。在单设备直连场景中通常为0;在多设备、多通道或通过网关转发的场景中,它用于区分不同设备。解析时要注意:设备ID是“逻辑设备标识”,不一定等于物理站号,具体含义由设备厂商的GEM手册定义。

事务ID(Transaction ID)占4字节,是主机或设备为一次“请求—回复”配对而分配的编号。例如主机发送S1F13时使用事务ID=0x00000001,设备回复S1F14时必须携带同一个事务ID,这样主机才能把回复与请求对应起来。事务ID由发起方维护,通常循环递增,避免在同一时间窗口内重复。若事务ID不匹配,上层软件会认为收到了“孤儿回复”,从而丢弃或报错。

三、W-bit:需不需要回复

W-bit(Wait-bit)位于Stream字节的最高位。它回答一个关键问题:这条消息是否需要对方回复?

  • W-bit = 1:发送方等待回复,接收方必须用同一Stream、Function+1的消息回复。例如S1F13的W-bit=1,设备必须回S1F14。
  • W-bit = 0:发送方不要求回复,接收方处理完即可,不产生配对消息。例如S6F11事件报告通常W-bit=0。

实际解析中,W-bit与Function码共同决定通信方向。以S1F13/S1F14为例:S1F13是主机发起的“建立通信请求”,W-bit=1;S1F14是设备回复的“建立通信确认”,其W-bit通常为0。若在日志中看到S1F13的W-bit=0,就说明发送方不等待回复,这通常不符合GEM规范,需要排查发送端实现。

四、系统字节:多块消息的拼接控制

系统字节(System Bytes)占1字节,其中最高位是结束位(End-bit),低7位是系统字节值。它的核心作用是支持多块消息(Multi-block Message)的传输。

当一条消息的数据区超过单条消息允许的最大长度时,发送方会把它拆成多个块。每个块都有自己的消息头,End-bit=0表示“后面还有块”,End-bit=1表示“这是最后一块”。接收方按系统字节值把同一事务的多个块重新组装成完整消息。对于大多数常规SECS-II消息,数据区较短,End-bit通常为1,系统字节值为0。

解析日志时,如果看到同一事务ID下出现多条End-bit=0的消息,不要急于报错,这很可能是正常的多块传输。应继续收集后续块,直到End-bit=1再拼接数据区。

五、流与功能码:消息的“姓”和“名”

Stream(流)和Function(功能码)共同定义消息的语义。Stream占1字节的低7位,取值通常为1~127;Function占1字节,取值通常为0~255。二者组合成“SxFy”的记法,例如S1F1、S2F41、S6F11。

消息方向含义W-bit
S1F1主机 → 设备Are You There 请求1
S1F2设备 → 主机Are You There 确认0
S1F13主机 → 设备建立通信请求1
S1F14设备 → 主机建立通信确认0
S6F11设备 → 主机事件报告0
S2F41主机 → 设备远程命令1

规律是:偶数Function通常是奇数Function的回复,且回复消息的W-bit一般为0。但并非绝对,具体以GEM手册和实际设备定义为准。解析时先看Stream和Function确定消息类型,再看W-bit判断是否需要等待回复,最后看事务ID做配对。

六、消息长度与数据区:真正承载业务的部分

消息头最后一个字节是数据区长度(Length),它只表示数据区的字节数,不包含10字节消息头。例如Length=0x0C表示后面还有12字节数据区。接收方先读10字节头,再根据Length读取对应字节数的数据区,从而完成一条消息的切分。

数据区由若干SECS-II Item组成。每个Item的结构是:1字节Item头(高6位格式码,低2位长度字节数)+ 长度字段 + 数据内容。例如一个ASCII字符串Item,格式码为0x10,若长度用1字节表示,则Item头为0x41,后跟长度值,再跟字符串内容。多个Item可以嵌套,形成列表(List)结构,用于表达复杂数据。

示例:S1F13 数据区包含一个 List,内含 MDLN 和 SOFTREV 两个 ASCII Item
数据区字节(十六进制):
01 02 00 02 4D 44 00 06 31 2E 30 2E 30
解析:
01        → List,长度1字节
02        → List 长度 = 2 个 Item
00 02     → ASCII,长度2字节
4D 44     → "MD"
00 06     → ASCII,长度6字节
31 2E 30 2E 30 → "1.0.0"

实际解析时,建议按“先头后体、先长度后内容”的顺序推进:读10字节头 → 取Length → 读Length字节数据区 → 按Item头逐层展开。遇到List时递归解析,遇到非List时按格式码解释数据。这样即使面对嵌套多层的复杂消息,也能稳定还原出完整的SECS-II结构。

常见错误:把Length理解成“整条消息长度”。若按此解析,会把消息头也算进去,导致数据区偏移10字节,后续Item全部错位。务必记住:Length只描述数据区。

把消息头10字节的每个字段、W-bit与Stream的位分配、事务ID的配对作用、系统字节的多块控制、Length与数据区的边界关系串起来,就形成了SECS-II消息解析的完整骨架。后续无论是抓包分析、构造指令,还是排查“回复对不上”“数据区解析乱码”等问题,都可以回到这张结构图上逐字段核对。