☰
SECS/GEM协议实战:手写Python通信Demo,打通设备与Host互联
2026/10/4 2:03:45 网站建设 项目流程

干这行的人应该都有体会,头一回接触SECS协议,十有八九是被项目逼的。设备已经躺在车间里了,EAP(Equipment Automation Program,设备自动化程序)的上位机软件写着写着卡住了,SECS/GEM那几个缩写翻来覆去查了不少资料,真正能用来指导落地的却没几篇。我自己当年从纯IT转过来做半导体设备互联,拿到手的第一个任务就是“把设备接进MES系统”,那时候连S1F13是干什么的都不知道,硬着头皮啃了半个月SEMI标准文档,才把最基础的通信链路调通。所以这系列教程,我不会给你翻译标准文档,而是以“我要把一台设备跑起来、让Host和Equipment能对上话”这个目标为线索,把SECS协议的核心逻辑、消息结构、实操踩坑全部梳理清楚。第一期先讲基础框架和第一个能跑的通信Demo,适合刚接手半导体、光伏、锂电设备对接项目的软件工程师,也适合想了解工厂自动化底层通信原理的开发者。

1. 先搞清楚SECS协议家族到底在解决什么问题

1.1 会议室里聊的“SECS/GEM”,其实是一整套协议族

很多刚入门的人会把SECS当作一个协议,实际上它是一整套标准体系,SEMI组织(Semiconductor Equipment and Materials International,国际半导体设备与材料协会)从上世纪80年代开始持续发布。核心标准包括:

  • SEMI E4(SECS-I):定义了基于RS-232串口的物理传输协议,规定了数据怎么拆成块、怎么校验、超时重发等底层机制。
  • SEMI E5(SECS-II):定义了消息层协议,解决的是“消息长什么样”——消息头怎么编码、数据项用什么格式、Stream/Function如何分配。
  • SEMI E37(HSMS):定义了基于TCP/IP的传输协议,取代慢速串口,成为今天工厂自动化系统里最常用的传输方式。
  • SEMI E30(GEM):在SECS-II之上做了行为层面的约定,比如设备的状态模型、报警怎么上报、配方怎么管理,让不同厂商的设备在行为上保持高度一致。

用快递类比的话,SECS-I和HSMS是“运输货车”,负责把包裹从A点拉到B点;SECS-II是“包裹里的标准装箱单”,规定了什么货物必须放在什么位置、怎么打标签;GEM则是“快递公司服务流程规范”,规定上门取件要在什么时间、签收要盖什么章。四者配合,才能让设备厂商和EAP开发商在不直接沟通的情况下也能实现对接。

1.2 为什么设备厂商都愿意遵守这套标准

制造业设备种类极其繁杂——薄膜沉积、光刻、刻蚀、清洗、检测,每家厂商的机型都不一样。如果没有统一标准,EAP开发商每接入一种新设备,就要写一套专门的通信驱动,耗费巨大且极难维护。SECS/GEM的价值在于:它让设备和上位机之间形成了一套“通用语言”。设备厂商只要实现SECS/GEM标准,EAP开发商就可以用相对统一的代码框架去对接不同设备,差异化部分收敛到配置文件和少量的私有消息适配。

不过要提醒的是,标准归标准,现实中每家设备厂商对标准的理解和落地程度参差不齐。有些设备把S6F11事件上报的实现方式写得跟标准样例不太一样,有些设备在S1F13握手时返回的数据结构有特殊约定。所以在实操中,设备厂商提供的通信规格书(通常是一份SML文件加若干说明文档)往往比SEMI标准正文更值得信赖。

1.3 通信的两种角色:谁主动,谁被动

SECS协议里一台设备(Equipment)对应一台Host主机,通信双方有明确的角色分工:

  • Host(主机/上位机):通常是EAP、MES(Manufacturing Execution System,制造执行系统)或者专门的设备监控平台。它既做服务端监听设备连接,也会作为客户端主动下发指令。
  • Equipment(设备端):生产设备上的PC或控制器。设备上电后需要通过HSMS连接到Host,完成握手后,双方才能开始交换业务消息。

从网络连接模式看,HSMS有两种方向:设备端主动连接Host(ACTIVE模式,最常见),或者Host主动连接设备(PASSIVE模式)。这个选择不是随意的,通常由工厂的网络架构决定。如果设备在产线局域网内、Host中心化部署,那设备主动找Host是常态;如果设备处于隔离网段且不允许主动外连,可能就要Host反连了。

2. 消息层核心概念与报文解剖

2.1 Stream和Function:给消息编号的“门牌系统”

SECS-II消息层用Stream/Function二元组来标识每一条消息的具体用途。Stream是消息的大类,Function是具体的子功能。例如:

  • S1F13:通信建立请求(Establish Communications Request),由设备端发起握手。
  • S1F14:通信建立确认(Establish Communications Acknowledge),Host收到S1F13后回复。
  • S6F11:事件报告发送(Event Report Send),设备主动上报事件,例如“抓片器到位”“腔体抽真空完成”。
  • S2F17:日期时间请求(Date and Time Request),Host查询设备当前时间。

其中,S1F13是“Primary Message”(主消息),S1F14是对应的“Secondary Message”(次消息)。在协议交互里,Primary消息表示一次对话的发起方,Secondary消息表示应答方。判断哪条消息是Primary,关键看消息头里的W位(Wait Bit)。

2.2 W位:判断消息是请求还是应答

W位是消息头Stream字节的最高位(bit 7)。规则非常简单:

  • Primary消息发起时,W位置1,表示“我在请求你,请给我回复”。
  • Secondary消息回复时,W位必须为0,表示“这是响应,不是新的请求”。

举个例子:设备发S1F13时,消息头的Stream字段值是0x81(二进制1000 0001),其中低7位是Stream=1,最高位W=1。Host回复S1F14时,Stream字段值是0x01,W位为0。如果W位搞反了,接收方很可能直接把消息当协议错误丢弃。

另外,消息头里还有一个Message ID字段,由Primary消息发起方分配,通常是自增的。Secondary消息必须把Message ID原样带回,这样发起方才能把请求和响应配对。高并发场景下如果Message ID管理不当,很容易出现响应错配的问题。

2.3 10字节消息头逐字节拆解

以HSMS传输的SECS-II消息为例,一条消息的完整结构是:4字节的总长度 + 10字节的消息头 + 可选的Data区。消息头的10字节分布如下:

字节位置字段说明
0-1Session ID会话ID。对SECS-II消息来说,通常就是Device ID(设备ID)
2Stream低7位是Stream号,bit7是W位
3FunctionFunction号
4-5Message ID消息ID,大端序,由请求方分配
6PType消息类型,0表示SECS-II消息
7SType0表示SECS-II业务消息,非0表示HSMS控制消息(如Select、Deregister)
8-9保留通常为0

这里特别容易混淆的是Session ID和Device ID的关系。在绝大多数设备对接场景里,一个TCP连接只对应一台设备,Session ID就等于配置的Device ID;但对支持多路复用(一个Host连接多个设备)的HSMS实现来说,Session ID用于区分不同设备。如果你的设备规格书里提到“Session ID”和“Device ID”,一定要确认清楚设备侧对这两个值的处理方式。

2.4 数据项与格式码:消息正文的编码规则

SECS-II消息的Data区由若干个数据项(Data Item)组成,每个数据项由三部分组成:格式码(Format Code)、长度(Length)、数据(Data)。常见的格式码如下:

格式码类型示例说明
0x20List列表,可包含多个子数据项
0x25ASCII字符串,如设备型号“EQUIP-01”
0x29Binary二进制数据
0x40INT1(1字节有符号整数)小整数
0x41INT2(2字节有符号整数)短整数
0x42INT4(4字节有符号整数)常规整数
0x44UINT1(1字节无符号整数)通常用于状态码
0x48FLOAT4(4字节浮点数)测量值等

长度字段的编码规则稍微特殊:当数据长度小于256字节时,用一个字节表示;当长度超过255字节时,用三个字节表示,第一个字节固定为0x81,后两个字节是大端序的真实长度。实战中很多报文长度并不长,但如果你处理的设备有大量配方数据或历史追溯数据,就一定会遇到3字节长度编码的情况。

2.5 一条SML消息长什么样

设备厂商通常会提供一份SML文件(SECS Message Language,SECS消息语言),用来描述设备支持的消息模板。SML内容大概长这样:

S1F13 { <MDLN "EQUIP-01"> <SOFTREV "1.0.0"> }

这表示S1F13这条消息携带两个数据项:MDLN(设备型号)和SOFTREV(软件版本),都是ASCII字符串。在开发EAP或设备端软件时,SML文件是双方核对消息结构的“合同”。收到一份SML后,先不要急着写代码,把每个消息的Stream/Function、数据项名称、类型、顺序都逐条理清楚,能省掉后续大量联调时间。

3. 开发前的准备工作

3.1 明确你的角色:设备端还是主机端

开发SECS通信程序,第一步不是写代码,而是搞清楚你站在协议交互的哪一边。如果做的是设备端,你的任务是响应Host的各种请求,主动上报设备状态;如果做的是Host端(EAP),你的任务主要是监听设备连接、解析设备上报的数据、下发控制指令。角色不同,选用的开发库、关注的消息类型、调试手段都会不一样。

这里特别想说一句:很多工程师在刚接触SECS时,会把大量精力花在研究协议的细枝末节上,结果迟迟写不出一个能通信的程序。正确的做法是先用最简单的方式跑通一条消息(比如S1F13/S1F14),再逐步扩展业务功能。协议细节是在踩坑中慢慢熟悉的,不是靠死啃文档啃出来的。

3.2 开源库和商业SDK怎么选

市面上主流的SECS/GEM开发方案分两类:

  • 开源库:比如Python的secsgem、C#的Secs4Net、Java的某些开源HSMS实现。优点是免费、源码透明、可以自己调试底层细节;缺点是文档参差不齐,遇到问题需要翻源码。
  • 商业SDK:比如很多半导体设备厂商和EAP集成商使用的商业组件,优点是有技术支持、稳定性验证过;缺点是收费不低,而且有些闭源组件出了问题不好排查。

对学习阶段和Demo验证来说,开源库完全够用。我在后面的示例中故意用“裸socket”方式实现,目的就是让你看清消息组装和解析的每一步,搞懂原理后再用开源库就非常快了。

3.3 搭建一个最小联调环境

在写代码之前,建议先准备好三样东西:

  1. SECS/GEM模拟器。网上能搜到不少免费的模拟器,有些可以直接模拟设备端,有些可以模拟Host端。建议至少准备两种:一个用来模拟对端,一个作为调试参考。
  2. 抓包工具。Wireshark对HSMS这种基于TCP的协议非常有用,可以按端口过滤,直接看到消息的十六进制内容。必要时写个小脚本把报文按HSMS格式解析出来,方便核对。
  3. 一份待对接设备的SML或通信规格书。如果没有真实设备,就从模拟器自带的SML入手。

我的经验是,环境搭建阶段就花时间把模拟器的连接、消息发送、断线重连等基本操作玩熟,后面面对真实设备时心里会稳很多。很多联调现场的问题,本质上就是“对端协议行为和预期不一致”,而模拟器能帮你提前预演这些差异。

4. 用Python跑通第一个SECS通信

4.1 通信流程速览:先握手,再干活

在最常见的设备主动连接Host场景下,完整通信流程是:

  1. 设备端和Host建立TCP连接。
  2. 设备端发送HSMS控制消息Select Request,请求建立HSMS会话。
  3. Host回复Select Response,结果码为0表示成功。
  4. 设备端发送S1F13(通信建立请求)。
  5. Host回复S1F14(通信建立确认)。
  6. 握手完成,双方开始收发S6F11、S2F17等业务消息。

下面我用Python的socket实现一个最小Host端Demo,重点演示第4、5步。为了让你看清消息结构,这里不依赖任何SECS库,直接操作字节。

4.2 最小Host端实现(可运行的Demo)

import socket import struct LISTEN_IP = "0.0.0.0" LISTEN_PORT = 5000 def recv_n(conn, n): data = b"" while len(data) < n: chunk = conn.recv(n - len(data)) if not chunk: raise ConnectionError("connection closed by peer") data += chunk return data def recv_hsms(conn): # 先读4字节总长度,再读完整消息 len_bytes = recv_n(conn, 4) total_len = struct.unpack(">I", len_bytes)[0] message = recv_n(conn, total_len) header = message[:10] body = message[10:] return header, body def send_hsms(conn, header, body=b""): packet = struct.pack(">I", 10 + len(body)) + header + body conn.sendall(packet) # 创建TCP服务端 server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) server.bind((LISTEN_IP, LISTEN_PORT)) server.listen(1) print("Host listening on port", LISTEN_PORT) conn, addr = server.accept() print("Device connected from", addr) # 第1步:接收Select Request(HSMS控制消息,SType=1) header, body = recv_hsms(conn) session_id, stream_fn, func, msg_id = struct.unpack(">HBBH", header[:6]) s_type = header[7] print("Select Request, SType=%d, MsgID=%d" % (s_type, msg_id)) if s_type == 1: # 回复Select Response,SType=2,Body为一个字节0表示成功 resp_header = struct.pack(">HBBH", 0xFFFF, 0x00, 0x00, msg_id) + b"\x00\x02\x00\x00" send_hsms(conn, resp_header, b"\x00") # 第2步:接收S1F13(SECS-II业务消息,SType=0) header, body = recv_hsms(conn) session_id, stream_fn, func, msg_id = struct.unpack(">HBBH", header[:6]) w_bit = (stream_fn & 0x80) >> 7 stream = stream_fn & 0x7F print("Received S%dF%d, W=%d, DeviceID=%d" % (stream, func, w_bit, session_id)) if stream == 1 and func == 13 and w_bit == 1: # 第3步:回复S1F14,W位清0,Message ID原样返回 resp_header = struct.pack(">HBBH", session_id, 0x01, 0x0E, msg_id) + b"\x00" * 4 send_hsms(conn, resp_header) print("Replied S1F14") # 保持连接,等待后续消息 try: while True: header, body = recv_hsms(conn) session_id, stream_fn, func, msg_id = struct.unpack(">HBBH", header[:6]) stream = stream_fn & 0x7F w_bit = (stream_fn & 0x80) >> 7 print("Received S%dF%d, W=%d, body_len=%d" % (stream, func, w_bit, len(body))) except Exception as e: print("Connection closed:", e) finally: conn.close() server.close()

这段代码的核心逻辑是:先等TCP连接,然后依次处理Select Request和S1F13,最后回复S1F14。代码里最关键的几个点,再提醒一下:

  • 消息头第8和第9字节覆盖了SType和保留字段,发送Select Response时Header要拼成struct.pack(">HBBH", ...) + b"\x00\x02\x00\x00",顺序是PType=0、SType=2、保留=0。写错一个字节,对端就直接丢弃消息。
  • S1F14的Session ID要回显设备发来的值,而不是生硬地用配置的Device ID。虽然在单设备场景下两者相等,但做个好习惯,回显更稳妥。
  • W位处理是消息方向的关键,用来解析请求/响应关系。回复时W位一定要置0,否则对端会认为你又发起了一个新的Primary消息,导致无法成功配对。

4.3 设备端需要做的事

设备端的逻辑和Host端是对称的:设备先主动连上Host,发送Select Request,然后发送S1F13,等待Host回复S1F14。设备端代码这里不展开,因为核心的字节拼装逻辑和Host端几乎一致,区别在于:

  • 设备端Socket角色的目标是Host的监听端口,所以写的是connect()而不是accept()。
  • 设备端在发送Select Request时,Message ID可以自己从1开始递增。
  • 设备端在收到S1F14后,需要检查W位是否为0、Message ID是否和S1F13一致,确认无误才算握手成功。

4.4 抓包验证:用Wireshark看清消息长什么样

跑通Demo之后,强烈建议你用Wireshark抓一次包。过滤条件直接写tcp.port == 5000,然后逐条看TCP payload,对照上面代码里的字节结构,一目了然。

正常情况下你会看到四条消息:

方向内容说明
Equipment → HostSelect Request10字节头,SType=1
Host → EquipmentSelect Response10字节头 + Body(0x00)
Equipment → HostS1F1310字节头 + 两个ASCII数据项(MDLN、SOFTREV)
Host → EquipmentS1F1410字节头,无Body

如果抓包时发现S1F13的Body里数据项顺序或编码不对,用前面讲的格式码规则逐一对照,通常能很快定位问题。我见过不少同事在联调时靠grep打印日志排查,其实把Wireshark报文和代码日志结合起来看,效率会高出一大截。

5. 开发中常见的坑与排查技巧

5.1 连接失败类的坑

现象可能原因排查方法
设备连接不上Host端口Host端口没监听,或防火墙拦截确认监听状态,临时关防火墙验证
连接建立后立刻断开Select流程没有完成,或T7超时抓包看是否收到Select Response和S1F13
Select Response发出去设备不认Header里SType字节拼错用Wireshark对照标准,确认SType=2
设备反复重连T5(连接失败重试间隔)设置太短查看设备端日志,适当调大连接重试间隔

5.2 消息配对和超时:T3和T7最让人头疼

SECS协议定义了一组定时器,这里重点讲两个:

  • T3:设备发出一条Primary消息后,等待Secondary响应的超时时间,典型值是45秒。如果Host业务处理太慢,超过T3还没回复,设备会认为消息丢失并主动断开连接或重发。EAP端如果发现设备频繁断连,先看是不是T3超时了。
  • T7:在HSMS连接上,如果双方在一定时间内(典型值120秒)没有发送任何消息,连接会被认为失效。所以Host和设备之间往往需要心跳消息来保活。常见的做法是周期发送S1F3(设备状态请求)或者S2F17(日期时间请求),把连接维持在活跃状态。

我踩过一个真实的坑:在一台设备对接项目中,设备的T3配置特别短(只有15秒),而我们的EAP在收到S6F11事件后,因为上游系统响应慢,处理时间超过了T3,结果设备端不断重发事件,数据库里出现大量重复报警记录。后来在EAP侧优化了事件处理链路的响应速度,并把设备端T3适当调大,问题才消除。排查这类问题,关键是看设备端日志里是否有“Wait Reply Timeout”之类的关键词。

5.3 消息格式和业务语义的坑

  • Format Code不匹配:设备发的数据项类型和SML里定义不一致,例如SML写的是ASCII“123”,设备实际发来的是INT4数值123。解析时如果直接强转,轻则拿到乱码,重则触发异常。所以解析消息体时要做类型检查,不能假设对端一定按文档来。
  • List嵌套层级不对:有些消息的数据项是嵌套的List,比如S6F11里每个事件项都是一个List,里面又有多个数据项。层级少套一层或多套一层,解析结果完全不对。写解析代码时建议先打印原始数据项的层级结构,再逐层处理。
  • 设备ID没对上:Device ID配置错是最常见的问题。Host配置的设备ID和设备侧发来的不一致,消息会被直接丢弃。联调时第一个要核对的就是这个东西。

5.4 找不到问题时的兜底办法

如果前面所有排查手段都用了还是找不到问题,我建议你做两件事:

  1. 用模拟器复现同样的交互流程。把真实设备替换成模拟器,跑相同的消息序列,看模拟器表现是否和真实设备一致。如果模拟器能正常交互而真实设备不行,问题大概率在设备侧实现。
  2. 抓包对比标准消息结构。把抓包得到的十六进制字节手工解码,逐字节对照SGEM标准文档或SML。很多时候,问题就出在一个毫不起眼的字节上,比如长度字段用了几字节、某个保留位没有置0、Header里的Message ID没有回显。

写在最后

第一次跑通S1F13/S1F14的时候,可能感觉也就那样,不就是几条TCP消息嘛。但实际上,这套“裸socket”走一遍的价值在于:你真实地看见了消息头的每一个字节、W位的置位和清零、Message ID的回显规则,这些细节是你在用开源库时被隐藏掉的。后续不管你是转向secsgem这类封装好的库,还是直接基于厂商SDK开发,对消息结构的理解都会成为你排查问题时最硬的本钱。

我个人在实际项目中还有一个习惯:每接触一种新设备,第一件事就是把设备的SML文件里所有消息过一遍,把常用的S1、S2、S5、S6、S7系列消息整理成一个速查表,标注好消息方向、数据项格式和业务含义。对接的设备多了以后,你会发现大部分问题不是协议本身有多难,而是细节的差异。下一期我会挑一条业务消息(比如S6F11事件上报)来完整讲一讲从设备上报到Host解析、入库的全过程,顺便聊聊EAP侧消息处理线程模型怎么设计才不容易被协议细节堵死。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询