简介:这份资源是面向半导体设备自动化领域开发者的 SECS/GEM 协议源码包,适合从事 EAP 系统开发、设备通信对接的工程师及希望深入理解 SEMI 标准的进阶学习者。它解决了协议实现细节不透明、缺少可运行参考代码的问题,可用于搭建设备与主机之间的通信框架、研究状态机与消息编解码逻辑。压缩包共 97 个文件,约 158KB,以 46 个 Python 源码文件为核心,覆盖 GEM、SECS、HSMS 等模块及配套测试用例;另有 38 个 rst 文档用于说明安装、参考与快速上手,辅以 yml、sh、Makefile 等构建与配置脚本,整体结构清晰、便于按模块研读。目前已有 791 人学习下载。读者可从中获得完整的协议分层实现、连接状态模型、数据项与事件处理示例,以及可复用的测试脚本,为二次开发与问题排查提供直接参考。
1. 从 secsgem-master.zip 说起:半导体 EAP 为什么绕不开 SECS/GEM
一条 12 寸产线上,几十台设备来自不同年代、不同厂商,上位机却要统一拿到它们的运行状态、配方、报警和良率数据。设备侧只认 SECS/GEM 这套 SEMI 标准协议,而 MES、EAP 侧往往是 Java、C# 或 Python 写的业务系统。中间这层协议转换,就是 EAP(Equipment Automation Program)存在的理由。secsgem-master.zip这类源码包,本质是把 SECS/GEM 的报文编解码、状态机、超时重传、事件上报封装成可调用的库,让工程师不用从 HSMS 字节流开始手搓。它解决的是「设备说 SEMI 方言、业务系统说 HTTP/JSON」的翻译问题,适合做设备自动化、EAP 开发、产线数据采集的工程师,也适合想搞懂 SECS 到底是单向还是双向的协议初学者。
2. SECS/GEM 协议栈拆解与 secsgem 源码结构
2.1 SECS-I、HSMS 与 SECS-II 的分层关系
SECS/GEM 不是单一协议,而是三层叠起来的。最底下是传输层,早期用 SECS-I(基于 RS-232 串口,带块传输和握手),现在主流是 HSMS(High-Speed SECS Message Services,跑在 TCP/IP 上,默认端口 5000)。中间是 SECS-II,定义消息的语义结构:每条消息用 Stream 和 Function 编号标识,比如 S1F1 是「Are You There」、S1F2 是它的回复、S6F11 是事件报告。最上面是 GEM,规定设备必须实现哪些 Stream/Function、状态模型怎么走、报警和事件怎么管理。
理解这个分层,才能看懂secsgem-master.zip里的目录。常见做法是:hsms或secs目录放传输层和消息编解码,gem目录放 GEM 状态机和高层接口,clients/servers放主动连接和被动监听两种角色。SECS 是双向的——设备可以主动发 S6F11 上报事件,主机也可以主动发 S2F41 下发远程命令,谁先发取决于角色和场景,不存在「只能主机问、设备答」的单向限制。
2.2 从源码目录定位核心模块
拿到源码包先别急着跑,先看结构。以 Python 实现的 secsgem 为例,典型布局如下:
secsgem-master/ ├── secsgem/ │ ├── secs/ │ │ ├── hsms.py # HSMS 连接、状态机、超时 │ │ ├── secs2.py # SECS-II 数据类型编解码 │ │ └── functions.py # Stream/Function 基类 │ ├── gem/ │ │ ├── gem.py # GEM 设备/主机行为 │ │ ├── handler.py # 事件、报警、数据变量回调 │ │ └── stream_function.py │ └── common/ │ └── settings.py # 连接参数、超时、设备 ID ├── samples/ # 官方示例,先跑这个 └── tests/ # 单元测试,看协议边界samples/目录是最有价值的入口,它通常包含一个最小可运行的设备端和主机端。先读 sample 再读核心库,比直接啃hsms.py效率高得多。
2.3 消息编解码:SECS-II 数据类型怎么落到代码
SECS-II 的数据项有明确类型:L(List)、A(ASCII)、B(Binary)、Boolean、U1/U2/U4/U8(无符号整数)、I1/I2/I4/I8(有符号整数)、F4/F8(浮点)。源码里通常有一个SecsVar基类和一堆子类,负责把 Python 对象和字节流互转。
from secsgem.secs.secs2 import Secs2 from secsgem.secs.variables import U4, A, L, Boolean # 构造一个 S1F2 的回复体:设备 ID + 在线状态 body = L( A("EQP001"), # 设备 ID,ASCII 类型 Boolean(True), # 是否在线 U4(12345) # 累计处理晶圆数 ) encoded = body.encode() print(encoded.hex()) # 输出 SECS-II 字节流L是列表容器,可以嵌套;A存字符串;U4是 4 字节无符号整数。编码后的字节流再交给 HSMS 层加上 10 字节消息头(含 Session ID、Stream、Function、Block 号等),才成为完整报文。参数上要注意:整数类型选错会导致设备解析失败,比如设备期望 U2 你发了 U4,长度对不上直接报错。
3. 用 secsgem 跑通第一个 HSMS 连接
3.1 环境准备与依赖安装
先确认 Python 版本,secsgem 一般要求 3.7 以上。用虚拟环境隔离依赖:
python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate pip install -r requirements.txt # 源码包自带 # 或者直接装 pip install secsgem如果源码包里有setup.py,用pip install -e .以可编辑模式安装,方便改代码调试。装完先跑pytest tests/确认环境没问题,测试全绿再动手写业务。
3.2 设备端(Equipment)最小实现
设备端通常作为 HSMS 的被动方,监听端口等主机连进来。下面是一个最小设备端:
from secsgem.gem import GemEquipmentHandler from secsgem.secs.functions import SecsS01F01, SecsS01F02 from secsgem.secs.variables import A, L, Boolean class MyEquipment(GemEquipmentHandler): def __init__(self, settings): super().__init__(settings) # 注册 S1F1 的处理:主机问 Are You There self.register(SecsS01F01, self.on_s1f1) def on_s1f1(self, handler, message): # 回复 S1F2,带上设备 ID 和在线状态 return L(A("EQP001"), Boolean(True)) settings = { "address": "0.0.0.0", "port": 5000, "session_id": 0, "device_id": "EQP001", "is_equipment": True, } equipment = MyEquipment(settings) equipment.enable() equipment.serve_forever()register把 Stream/Function 和处理函数绑定,收到对应消息就回调。session_id在 HSMS 里用于区分连接,设备端和主机端必须一致。is_equipment=True决定角色行为,比如谁主动发 Select 请求。
3.3 主机端(Host)连接与 S1F1 握手
主机端作为主动方,连到设备 IP 和端口:
from secsgem.gem import GemHostHandler from secsgem.secs.functions import SecsS01F01 settings = { "address": "192.168.1.100", # 设备 IP "port": 5000, "session_id": 0, "device_id": "HOST01", "is_equipment": False, } host = GemHostHandler(settings) host.enable() # 主动发 S1F1 探测设备是否在线 response = host.send_and_waitfor_response(SecsS01F01()) print(response) # 应收到 S1F2,含设备 ID 和在线状态send_and_waitfor_response是同步调用,内部会等超时。如果设备没响应,先查网络连通性(telnet 设备IP 5000),再查session_id是否匹配,最后看设备端日志有没有收到 Select 请求。
3.4 关键连接参数与超时设置
| 参数 | 含义 | 常见值 | 调错后果 |
|---|---|---|---|
session_id | HSMS 会话标识 | 0 或设备编号 | 不匹配直接拒绝连接 |
port | HSMS 监听端口 | 5000 | 改错连不上 |
T3 | 回复超时 | 45 秒 | 太短误判断线 |
T5 | 连接分离超时 | 10 秒 | 影响重连速度 |
T6 | 控制消息超时 | 5 秒 | 影响 Select 握手 |
T7 | 未选择超时 | 10 秒 | 连接建立后未 Select 会断 |
T8 | 网络字符间超时 | 5 秒 | 影响大报文传输 |
这些 T 值在 SEMI 标准里有推荐范围,但不同设备厂商会改。调试时先把 T3 调大(比如 60 秒),确认通信正常后再收紧。T7 特别容易踩坑:连接建立后必须在 T7 内发 Select 请求,否则设备主动断开。
4. GEM 状态机、事件上报与远程命令实战
4.1 设备状态模型与 S1F3/S1F4 状态查询
GEM 定义了一套设备状态模型,主机通过 S1F3(Selected Equipment Status Request)查询状态变量,设备用 S1F4 回复。状态变量包括控制状态(Online/Offline)、处理状态(Idle/Running/Down)等。
from secsgem.secs.variables import U4, A, L # 设备端注册 S1F3 处理 def on_s1f3(self, handler, message): # message 里带主机想查的 SVID 列表 svids = message.get() # 比如 [1001, 1002] values = [] for svid in svids: if svid == 1001: values.append(A("Online")) elif svid == 1002: values.append(U4(42)) # 当前批次晶圆数 return L(*values)SVID(Status Variable ID)是设备侧定义的编号,主机必须知道每个编号对应什么。实际项目里会有一份 SVID 对照表,通常由设备厂商提供。查错时先确认 SVID 编号对不对,再看数据类型是否匹配。
4.2 事件上报 S6F11 与报告定义
设备主动上报是 SECS 双向性的典型体现。设备通过 S6F11(Event Report Send)把事件推给主机,事件里可以带一组报告(Report),报告里是数据变量(DV)。
# 设备端触发一个事件 def report_event(self): # CEID=2001 表示「批次开始」 # RPTID=3001 关联一组 DV self.trigger_event( ceid=2001, reports={ 3001: [A("LOT123"), U4(25), A("RecipeA")] } )主机端要提前用 S2F33(Define Report)和 S2F35(Link Event Report)定义好 CEID、RPTID、VID 的对应关系,否则收到 S6F11 也不知道数据含义。这是新手最容易漏的一步:光注册事件回调不够,报告定义必须先协商好。
4.3 远程命令 S2F41 与 S2F49 的落地
主机下发远程命令用 S2F41(Host Command Send),设备执行后回 S2F42。命令带参数,比如「开始加工」带配方名。
from secsgem.secs.functions import SecsS02F41 from secsgem.secs.variables import L, A # 主机端下发命令 command = SecsS02F41( L( A("START"), # 命令名 L(L(A("RECIPE"), A("RecipeA"))) # 参数列表 ) ) response = host.send_and_waitfor_response(command) # response 里 HCACK=0 表示命令被接受HCACK(Host Command Acknowledge)是关键返回码:0 表示接受,1 表示命令不存在,2 表示参数不对,3 表示当前状态不能执行。设备端实现时要根据实际状态返回正确的 HCACK,否则主机会误判。
4.4 报警管理 S5F1 与确认流程
设备报警用 S5F1(Alarm Report Send)上报,主机用 S5F2 确认。报警分两类:个人报警(Personal Alarm)和系统报警(System Alarm),ALCD 字节里的 bit 7 表示报警是「发生」还是「清除」。
# 设备端上报报警 def report_alarm(self, alarm_id, is_set): alcd = 0x80 if is_set else 0x00 # bit7=1 表示报警发生 self.send_alarm(alcd, alarm_id, "温度超限")主机收到 S5F1 后必须回 S5F2,否则设备可能重发。报警清除也要发一次 S5F1,ALCD 的 bit7 置 0。很多现场问题出在报警只报不清,导致主机侧报警列表一直挂着。
5. 联调排错与性能调优的实用技巧
5.1 用日志和抓包定位 HSMS 通信问题
secsgem 一般支持日志级别配置,把logging调到 DEBUG 能看到每条报文的十六进制和解析结果。如果日志不够,用tcpdump抓包:
sudo tcpdump -i eth0 -w secs.pcap port 5000抓完用 Wireshark 打开,过滤tcp.port == 5000。HSMS 报文头是 10 字节,前 4 字节是长度,接着是 Session ID(2 字节)、Header Byte 2/3(含 Stream/Function 和 W/B 位)、PType、SType、System Bytes。看 System Bytes 是否匹配请求和回复,能快速判断是设备没回还是回了但解析错。
5.2 常见错误码与超时排查顺序
遇到通信失败,按这个顺序查:
- 网络层:
ping设备 IP,telnet 设备IP 5000看端口通不通。 - HSMS 层:看有没有 Select 请求和 Select 响应,T7 超时通常是没发 Select。
- SECS-II 层:看 Stream/Function 是否注册,未注册的消息会被忽略或回 S9F5。
- GEM 层:看报告定义、SVID、CEID 是否和主机侧一致。
- 业务层:看 HCACK、ACKC5、ACKC6 等返回码。
S9 系列是错误消息:S9F1 是不认识的消息,S9F3 是不认识的 Stream,S9F5 是不认识的 Function,S9F7 是非法数据。设备收到无法处理的消息时应该回 S9,而不是静默丢弃。
5.3 高频消息下的连接稳定性调优
产线设备每秒可能发几十条 S6F11,如果每条都同步等回复,吞吐上不去。常见做法是:事件上报用异步发送,不等回复;只有命令类消息才同步等 ACK。secsgem 里通常有send和send_and_waitfor_response两个方法,事件用前者。
另外把 T3 适当调大,避免高负载下误判断线;T8 调大能容忍大报文传输慢。但 T 值不能无限大,否则真断线时恢复太慢。一般 T3 设 45 到 60 秒,T8 设 5 到 10 秒,具体看设备手册。
提示:不同厂商对 T 值的默认设置差异很大,联调前先拿到设备的 SECS/GEM 手册,按手册设,不要照搬示例。
5.4 用 pytest 写协议回归测试
协议改动后容易引入回归,建议用 pytest 写针对 Stream/Function 的单元测试:
from secsgem.secs.variables import L, A, U4 def test_s1f2_encode(): body = L(A("EQP001"), U4(100)) encoded = body.encode() decoded = L.decode(encoded) assert decoded[0].get() == "EQP001" assert decoded[1].get() == 100编解码对称性测试能挡住大部分数据类型错误。再往上可以 mock 一个 HSMS 连接,测消息收发流程。测试覆盖 S1F1/S1F2、S1F3/S1F4、S6F11、S2F41/S2F42 这几组核心消息,基本能覆盖 80% 的联调场景。
本文还有配套的精品资源,点击获取