4. SECS/GEM通信协议实战

在EAP系统开发中,SECS/GEM是设备与主机之间最核心的通信标准。理解协议分层、消息编码规则以及典型会话流程,是写出稳定通信代码的前提。本节从SECS-II消息结构入手,逐步展开HSMS连接建立、S1F1/S1F2与S1F13/S1F14交互,以及事件与报警上报的代码实现方式。

一、SECS-II消息结构:从数据项到消息体

SECS-II消息由消息头(Header)和消息体(Body)组成。消息头固定10字节,包含设备ID、消息ID(Stream/Function)、等待位(W-bit)、系统字节和状态字节。消息体则由若干数据项(Data Item)按顺序排列,每个数据项由格式字节和长度字节描述,再跟实际数据。

格式字节高6位表示格式码,低2位表示长度字节数。例如格式码0b101001表示ASCII字符串,长度字节数由低2位决定。常见格式码包括:

  • 0o20(0x10):二进制(Binary)
  • 0o21(0x11):布尔(Boolean)
  • 0o24(0x14):有符号整型(I1/I2/I4/I8)
  • 0o30(0x18):无符号整型(U1/U2/U4/U8)
  • 0o44(0x24):ASCII字符串
  • 0o00(0x00):列表(List),用于嵌套结构

以S1F1(Are You There)为例,其消息体通常为空列表,编码后为0x01 0x00(List,长度0)。而S1F2(On Line Data)的响应体可能包含设备型号、软件版本等ASCII字符串,编码时需先写List头,再依次写入各数据项。

示例:假设设备型号为“EAP-3000”,软件版本为“1.2.5”,S1F2消息体可编码为:

List (长度 = 2)
  ASCII "EAP-3000"  → 0x41 0x08 0x45 0x41 0x50 0x2D 0x33 0x30 0x30 0x30
  ASCII "1.2.5"     → 0x41 0x05 0x31 0x2E 0x32 0x2E 0x35

实际开发中,建议使用成熟的SECS-II编解码库(如Java的SECS4Net、Python的secsgem),避免手工拼接字节流。但理解编码规则有助于排查通信异常,例如长度字节数错误会导致接收方解析越界。

二、HSMS连接建立:从TCP到Select状态

HSMS(High-Speed SECS Message Services)基于TCP/IP,默认端口5000。连接建立分为三个阶段:TCP三次握手、HSMS Select请求/响应、进入Selected状态。只有进入Selected状态后,才能收发SECS-II消息。

HSMS控制消息使用10字节头,其中Session ID为0xFFFF,Stream/Function为0,PType=0,SType表示控制类型。关键SType包括:

  • 0x01:Select.req
  • 0x02:Select.rsp
  • 0x03:Deselect.req
  • 0x04:Deselect.rsp
  • 0x09:Linktest.req
  • 0x0A:Linktest.rsp

主动方(通常是EAP)发送Select.req,被动方(设备)回复Select.rsp,状态码0表示成功。若设备未就绪,可能返回状态码1(已激活)或2(未激活)。EAP需实现超时重试机制,典型超时时间为45秒,重试间隔5秒。

HSMS连接建立与SECS-II消息交互时序 EAP(主机) 设备(Equipment) TCP SYN TCP SYN-ACK TCP ACK Select.req (SType=0x01) Select.rsp (SType=0x02, 状态=0) S1F1 W (Are You There) S1F2 (On Line Data) S1F13 W (Establish Comm) S1F14 (Comm Ack)

三、S1F1/S1F2与S1F13/S1F14的代码实现

S1F1用于确认设备在线,S1F2返回设备信息。S1F13用于建立通信,S1F14返回通信确认。两者都带W-bit,要求对方回复。以下以Python secsgem库为例,展示核心代码结构。

from secsgem.hsms import HsmsConnection
from secsgem.secs import SecsStreamFunction

# 建立HSMS连接
conn = HsmsConnection(host="192.168.1.100", port=5000, active=True)
conn.connect()

# 发送S1F1并等待S1F2
s1f1 = SecsStreamFunction(1, 1, wbit=True)
s1f2 = conn.send_and_wait(s1f1, timeout=10)
if s1f2:
    print("设备在线,型号:", s1f2.body[0].value)
    print("软件版本:", s1f2.body[1].value)

# 发送S1F13并等待S1F14
s1f13 = SecsStreamFunction(1, 13, wbit=True)
s1f14 = conn.send_and_wait(s1f13, timeout=10)
if s1f14 and s1f14.body[0].value == 0:
    print("通信建立成功")

实际项目中,需将发送与等待封装为异步回调,避免阻塞主线程。同时要处理超时、重发和连接断开重连。建议为每个Stream/Function定义独立的处理函数,并维护会话状态机。

四、事件与报警上报

设备通过S6F11(Event Report)上报事件,通过S5F1(Alarm Report)上报报警。EAP需先通过S2F33(Define Report)和S2F35(Link Event Report)配置事件与报告ID的映射,再通过S2F37(Enable/Disable Event)使能事件。

典型流程:EAP发送S2F33定义报告,包含报告ID和VID列表;发送S2F35将报告ID与事件ID绑定;发送S2F37使能事件。设备触发事件后,主动发送S6F11,携带报告ID和对应数据。EAP收到后回复S6F12(Ack)。

提示:报警上报使用S5F1,包含报警代码、报警类型(如0x01表示个人安全,0x02表示设备安全)和报警文本。EAP需回复S5F2确认。报警通常需要与MES联动,触发工单或停机逻辑。

代码实现时,需注册事件回调。以下为S6F11处理示例:

def on_s6f11(handler, message):
    report_id = message.body[0].value
    event_id = message.body[1].value
    data = message.body[2]
    print(f"收到事件:报告ID={report_id}, 事件ID={event_id}")
    # 解析数据并更新EAP状态
    handler.send_response(message, SecsStreamFunction(6, 12))

conn.register_callback(6, 11, on_s6f11)

报警处理类似,注册S5F1回调并回复S5F2。注意:事件和报警的使能状态需在设备重启后重新配置,EAP应实现自动重连和重新使能逻辑。

五、实战要点与排障建议

  • 消息编码校验:使用Wireshark抓包时,注意HSMS头10字节与SECS-II体的分界。常见错误是长度字节数计算错误,导致解析偏移。
  • 连接状态管理:HSMS连接可能因网络抖动断开,需实现心跳(Linktest)和自动重连。Linktest.req/rep每30秒一次,超时3次则判定断线。
  • 事件使能顺序:必须先S2F33定义报告,再S2F35绑定事件,最后S2F37使能。顺序错误会导致设备不发送S6F11。
  • 报警去重:设备可能重复上报同一报警,EAP需根据报警代码和时间戳去重,避免频繁触发MES工单。

掌握以上内容后,即可完成EAP与设备之间基础的SECS/GEM通信链路搭建。实际开发中,建议先使用模拟器(如SECS Simulator)验证消息交互,再接入真实设备调试。