ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

从零搭建SECS/GEM设备通讯平台:HSMS、消息编解码与EAP对接实战

从零搭建SECS/GEM设备通讯平台:HSMS、消息编解码与EAP对接实战 在半导体和泛电子制造产线上摸爬滚打这么多年只要涉及“设备联网”“设备数据采集”“EAP 对接”这类需求话题最后一定会落到 SECS/GEM 上。刚入行的工程师听到 SECS/GEM 协议系统通讯平台第一反应往往是“又是一套协议”但真正上手之后会发现它更像是一整套设备与主机之间的“对话规范和交通规则”设备说什么、主机怎么问、变量怎么定义、告警怎么上报、配方怎么下发全都写在这套体系里。我这些年做过晶圆厂的 EAP 对接也做过封测厂的设备数据中台几乎每个项目都要重新搭一遍 SECS/GEM 通讯平台或者至少把已有的平台扒开重写一遍。这篇东西就把我从零搭建和踩坑的全过程摊开来讲从协议族结构、平台分层设计、消息机制细节一直到实际编码、联调、排查问题的完整链条。适合准备做设备联网的软件工程师、自动化工程师也适合想搞清楚“主机到底在跟设备聊什么”的产线技术人员参考。哪怕你之前只接触过 Modbus、OPC UA 这类协议看完也能顺着思路把 SECS/GEM 通讯平台搭起来。1. 从产线痛点说起设备通讯为什么要单独搭平台1.1 车间里那些“各说各话”的设备我第一次进封装车间做设备数据采集时最直观的感受就是设备太杂。光刻、刻蚀、清洗、检测、贴片、焊线不同年代、不同厂商的设备混在一条线上有的用串口有的走网口有的自带一套私有软件导出的数据格式还各不相同。那时候的做法很土一台设备配一个采集脚本脚本里写死 IP 和端口定时去读几个数然后往数据库里塞。设备一换型号脚本就得重写设备厂商升级固件通讯直接断掉排查半天才发现是数据项格式变了。问题的根源在于设备与主机之间缺少一个双方都认的“共同语言”。主机想问“你现在什么状态”设备得知道该怎么回答设备想主动说“我这边报警了”主机得知道这条消息长什么样、该解析哪几个字段。如果每家厂商都自定义一套那主机侧就要维护几十套解析逻辑维护成本高得离谱而且一旦人员流动这些私有协议就成了没人敢碰的黑盒。SECS/GEM 协议系统通讯平台要解决的正是这个“多对多”的通讯混乱问题。它把设备侧和主机侧都统一到同一套消息规范上让平台作为中间层屏蔽掉设备型号差异和链路差异向上提供统一的数据接口。1.2 SECS/GEM 到底是一套什么协议很多人把 SECS/GEM 当成一个协议其实它是一整个协议族由 SEMI 组织制定包含好几个标准文档。最核心的几块是SECS-ISEMI E4定义了基于串口的物理链路和消息传输SECS-IISEMI E5定义了消息的内容结构也就是“消息长什么样”HSMSSEMI E37把链路搬到了 TCP/IP 上成为现在主流的高速通讯方式GEMSEMI E30则定义了设备应该具备的标准行为和能力比如状态模型、报警管理、事件报告、变量采集、远程命令、配方管理等等。你可以这样理解它们的分工HSMS 负责“怎么把消息送到对面”SECS-II 负责“消息的内容怎么编码”GEM 负责“设备在什么场景下该发什么消息”。三者叠在一起才构成一套完整的设备通讯能力。实际项目里说的“SECS/GEM 通讯平台”通常就是把这套能力封装成一个可复用的软件中间件一边对接设备一边对接上层 MES、EAP、SPC 等系统。注意SECS-I 用的是 RS-232 串口现在新设备基本都用 HSMS 走网口但老产线上仍然有大量串口设备在跑。平台设计时最好把两种链路都抽象出来别把 HSMS 写死在业务逻辑里。1.3 通讯平台的边界它管什么不管什么搭平台之前必须先划边界否则很容易做成一个什么都想管、最后什么都管不好的怪物。我的经验是SECS/GEM 通讯平台的核心职责只有三件事链路管理建立、维持、断开与设备的连接、消息编解码把字节流解析成有语义的消息对象把消息对象编码成字节流、会话与状态管理维护设备的通讯状态和控制状态处理事件报告、变量采集的订阅关系。它不该管的是业务逻辑比如“这个批次该不该放行”“这个告警要不要停线”“这个配方参数合规不合规”这些属于上层 MES 或 EAP 的职责。平台只负责把设备的数据准确地拿上来把主机的指令准确地发下去。边界划清楚了平台才能做得薄、做得稳、做得可复用。我在第二个项目里就吃过亏把工艺判定逻辑塞进了通讯层结果换个产品型号就得改通讯代码最后不得不重构把业务逻辑全部上移到 EAP。2. 通讯平台的分层设计与模块拆解2.1 四层结构从网线到业务语义一套靠谱的 SECS/GEM 通讯平台我一般会拆成四层。最底层是传输层负责 TCP 连接或串口连接的管理HSMS 的 Select/Deselect、Linktest、连接超时都在这一层处理。往上是消息层负责 10 字节消息头的编解码、数据项的解析与构造这一层只认字节不认业务。再往上是会话层维护设备的状态机、System Bytes 的分配与匹配、事务Transaction的超时与重发。最上面是应用层也就是平台对外暴露的接口向上层系统提供“读取变量”“订阅事件”“下发命令”这些语义化操作。这样分层的好处是每层的变更影响范围可控。比如设备从串口换成网口只需要替换传输层实现比如要新增一个采集变量只需要动会话层和应用层的配置消息层完全不用碰。我在实际项目里用 Java 和 Python 都实现过这套结构Python 上手快适合做原型和中小规模产线Java 在并发设备数量多、需要长期稳定运行的场景更合适。2.2 核心模块的职责划分把四层结构对应到具体模块我通常会写下这么几块。模块职责关键实现点连接管理器管理设备连接生命周期主动/被动模式、断线重连、心跳消息编解码器字节流与消息对象互转10 字节头、数据项格式码事务管理器请求-应答匹配与超时System Bytes 分配、T3 超时状态机设备通讯/控制状态维护GEM 状态模型变量管理器SV/DV/ECV 的定义与缓存变量 ID 映射、采集周期事件管理器事件报告定义与订阅报告定义、链接、使能告警管理器告警上报与确认告警 ID、告警类型配方管理器配方上传下载PPID、格式校验命令执行器远程命令下发RCMD、参数编码这张表看着简单但每一块都有坑。比如事务管理器里的 System Bytes它是消息头里的 4 个字节用来把应答和请求配对。如果分配不当比如复用太快或者不回绕就会出现应答匹配错乱主机收到的数据莫名其妙对应到了别的请求上。我习惯用一个 32 位递增计数器到上限后回绕同时维护一个未完成事务表收到应答后查表匹配。2.3 连接与会话状态机怎么设计GEM 定义的状态模型是整套协议里最容易被忽视、又最容易出问题的地方。设备有两组状态通讯状态分 Disabled 和 Enabled控制状态分 Equipment Offline、Host Offline、Online Local、Online Remote。主机通过 S1F13/S1F14 建立通讯通过 S1F17/S1F18 请求在线设备用 S1F1/S1F2 确认在线。设计状态机时很多人只实现了“连上就发数据”忽略了状态切换的时序。实际联调中如果主机在设备还没进 Online Remote 就下发远程命令设备会直接拒绝。正确的做法是平台内部维护一个状态变量任何操作前先校验状态是否允许。我一般会在状态机上挂一个事件回调状态一变就通知上层系统上层再决定接下来做什么。实操心得状态切换一定要做超时保护。曾经遇到过设备进入 Online 后迟迟不回复 S1F2主机端事务一直挂起导致后续消息全部排队。后来我在状态机里加了超时回退超时就回到上一状态并重新握手问题再没复现过。3. 消息机制的核心细节与实操要点3.1 Stream 与 Function消息的“门牌号”SECS-II 用 Stream流和 Function功能来标识消息写成 SxFy比如 S1F1、S2F33、S6F11。Stream 表示消息类别Function 表示具体动作。奇数 Function 通常是请求偶数 Function 是同 Stream 内的应答但这只是惯例不是强规则。比如 S1F1 是主机问设备“你在吗”设备回 S1F2 并附带设备状态S2F33 是主机下发报告定义设备回 S2F34 确认。消息头是 10 个字节结构如下Byte 0-1 Session ID / Device ID Byte 2 Streambit0-6 W-bitbit7是否需要应答 Byte 3 Functionbit0-6 消息类型标志 Byte 4-7 System Bytes事务标识 Byte 8-9 保留W-bit 是很多人第一次写 SECS 会搞错的地方。它表示这条消息是否需要对方应答。主机发 S1F1W-bit 置 1设备必须回 S1F2如果主机只是单方面告知W-bit 置 0设备就不需要回。平台的会话层必须根据 W-bit 决定是否创建事务、是否启动 T3 超时定时器。实测下来W-bit 处理不好最典型的现象就是“消息发出去了但主机一直等应答等到 T3 超时”其实设备根本没打算回。3.2 数据项格式码与编码陷阱SECS-II 的消息体是嵌套的列表结构最外层通常是一个 List里面再套各种数据项。每个数据项有一个格式码标识它的类型和长度字节数。常见的格式码如下表。格式码类型说明0o00LList列表可嵌套0o10BBinary二进制0o11BOOLEAN布尔值0o20AASCII 字符串0o30I88 字节有符号整型0o31I11 字节有符号整型0o32I22 字节有符号整型0o34I44 字节有符号整型0o40F88 字节浮点0o44F44 字节浮点0o50U88 字节无符号整型0o51U11 字节无符号整型0o52U22 字节无符号整型0o54U44 字节无符号整型这里的坑非常多。第一是字节序SECS-II 采用大端序也就是高位字节在前。很多用惯了小端序的工程师第一次解析 I4 会得到离谱的数值八成就是字节序搞反了。第二是长度字节数据项的长度字段可以是 1 到 3 个字节最高位为 1 表示后面还有长度字节。比如长度为 200 时会编码成两个长度字节。第三是List 没有长度字节数的限制List 的长度表示里面包含多少个元素而不是字节数这点和字符串完全不一样。def parse_item(data, offset): # 读取长度字节最多 3 个 length data[offset] num_len_bytes 1 offset 1 while length 0x80: length (length 0x7F) 8 | data[offset] offset 1 num_len_bytes 1 fmt data[offset] offset 1 fmt_code fmt 2 fmt_len_bytes fmt 0x03 # 根据格式码解析实际数据 ...上面这段是我从自己平台里摘出来的解析骨架实际写的时候要把 List 的递归处理加进去。我见过有团队直接用一个字典把格式码映射到 struct 格式结果 List 类型处理不了嵌套最后返工重写。建议一开始就把解析器设计成递归结构。3.3 事件报告与变量采集的落地方式GEM 里最常用、也最能体现平台价值的功能是事件报告和变量采集。设备侧会定义一批状态变量SV、数据变量DV和设备常量ECV主机通过 S1F3 读取指定变量或者通过事件报告机制订阅。事件报告的工作流程大致分三步。第一步主机用 S2F33 给设备下发报告定义告诉设备“报告 ID 为 1001 的报告里包含变量 1、变量 2、变量 3”。第二步主机用 S2F35 把报告链接到某个事件上告诉设备“当事件 2001 发生时发送报告 1001”。第三步主机用 S2F37 使能事件报告设备开始在上报事件时主动发 S6F11。这个流程听起来清晰但实操中容易出错的地方很多。最典型的是报告定义和链接的时序。如果主机先链接再定义设备会因为找不到报告 ID 而报错。正确的顺序是先 S2F33 定义确认后再 S2F35 链接最后 S2F37 使能。我在联调时写过一个检查清单每次新设备接入都照着走一遍基本能避免 90% 的配置问题。变量采集还有缓存策略的问题。设备采样频率高如果每次采集都往数据库写数据库压力会很大。我的做法是平台层做一次内存缓存按变量 ID 存最近值再按配置的落库周期批量写入。对于变化频繁的变量还可以做变化触发上报值不变就不写这样能显著降低数据量。3.4 告警、远程命令与配方管理告警上报走的是 S5F1消息体里包含告警 ID、告警码、告警类型和告警文本。平台收到 S5F1 后除了上报给上层还要负责调用 S5F3 使能或禁用告警、S5F5 查询告警列表。我一般会在平台里维护一份告警字典把数字告警 ID 映射成可读的告警描述上层拿到的就是“腔体温度超限”而不是“10023”。远程命令走 S2F41主机下发命令名和一组参数设备回 S2F42 带执行结果。这里的关键是命令参数的编码参数名和参数值要按设备定义的格式来写错了设备会返回 HCACK 非零的错误码。我的经验是每个命令都做成配置项把命令名、参数名、参数类型写在配置文件里平台按配置编码避免硬编码。配方管理涉及 S7F3/S7F5 下发和 S7F1/S7F2 上传配方体通常是 ASCII 字符串里面是工艺参数列表。配方要在平台上做版本管理和格式校验防止下发一个格式不对的配方把设备搞停。我一般会在平台上加一道校验配方里的参数数量和类型必须和设备声明的配方格式一致不一致直接拒绝下发。4. 从零搭一个能跑通的通讯平台4.1 环境准备与依赖选型先说环境。我一般用 Linux 服务器做平台运行环境Python 3.9 以上或者 Java 11 以上。Python 的生态里有一些现成的 SECS/GEM 库能省掉不少底层编解码的活Java 这边也有一些开源实现。但我的建议是即使有现成库核心的消息编解码和事务管理也要自己吃透一遍因为实际项目里设备厂商经常有一些非标准的扩展现成库不一定覆盖得到。网络方面HSMS 走 TCP默认端口在 SEMI E37 里约定实际项目里设备厂商通常会指定端口。平台和设备的网络要保证互通如果是跨网段要提前确认路由和防火墙策略。串口设备则要确认波特率、数据位、停止位和校验方式这些在 SECS-I 里都有约定但设备实际配置可能和文档不一致一定要现场核对。注意不要指望设备厂商文档里的端口和参数一定准确。我遇到过三次文档写的端口和实际不一致现场抓包抓了半天才定位。建议接入前先用 telnet 或抓包工具确认端口可连。4.2 HSMS 连接建立与握手细节HSMS 建连分两个阶段TCP 连接建立和 HSMS 会话选择。TCP 三次握手完成后还不能直接发 SECS 消息必须先发 Select.req设备回 Select.rsp 后才算进入 Selected 状态此时才能收发 SECS-II 消息。HSMS 的控制消息有几种Select.req/Select.rsp 用于会话选择Deselect.req/Deselect.rsp 用于取消选择Linktest.req/Linktest.rsp 用于心跳保活。Select.req 的消息头里Byte 3 是 SType 值 1System Bytes 用于匹配应答。链路空闲时双方都可以发 Linktest.req 探测对方是否还在线。HSMS 有几个关键定时器必须实现T3 是应答超时默认 45 秒T5 是连接分离超时默认 10 秒T6 是控制事务超时默认 5 秒T7 是未选择状态超时默认 10 秒T8 是字符间超时默认 5 秒。这些定时器的作用是防止连接假死。我曾经遇到过设备侧网络中断但 TCP 没有及时感知平台一直以为连接正常直到 T7 超时才触发重连。如果这些定时器没实现平台就会一直挂在一个已经死掉的连接上。class HsmsConnection: def connect(self): self.sock socket.create_connection((self.ip, self.port), timeout5) self.state CONNECTED self.send_select_req() def send_select_req(self): header self.build_header(session_id0xFFFF, stream0, func0, system_bytesself.next_sys_bytes(), stype1) # 1 Select.req self.sock.sendall(header) self.start_timer(T6, 5)上面是连接建立的核心骨架实际还要处理 Select.rsp 的匹配和异常分支。Select.rsp 的 Byte 3 是 SType2Byte 2 的高位表示选择结果0 表示成功非 0 表示失败。4.3 消息收发主循环的实现消息收发我建议用独立的收发线程加一个事务表。接收线程负责从 socket 读字节拼成完整消息后交给解析器发送线程负责把平台产生的消息编码后写入 socket。事务管理器维护一个 map键是 System Bytes值是待匹配的事务对象和超时时间。def on_message_received(self, msg): if msg.is_reply(): txn self.txn_map.pop(msg.system_bytes, None) if txn: txn.complete(msg) else: handler self.handlers.get((msg.stream, msg.function)) if handler: reply handler(msg) if msg.w_bit: reply.system_bytes msg.system_bytes self.send(reply)这段逻辑看着简单但要注意并发安全。事务表在多线程下要加锁否则可能出现两个线程同时操作同一个事务对象。另外消息解析要做长度校验防止半包和粘包。TCP 是流式协议不能假设一次 recv 就是一条完整消息必须先读 10 字节头再根据头里的长度字段读消息体。我在实际项目里踩过一个坑设备发过来的消息体里有一个超长的 List长度字段用了 3 个字节我的解析器只处理了 1 个字节的情况导致解析越界崩溃。后来把长度字段的处理改成循环读取问题解决。所以长度字段的处理一定要写全。4.4 数据落库与上层系统对接平台跑通后最后一步是把数据用起来。我一般会在平台和上层系统之间加一个消息队列平台把采集到的变量、事件、告警发布到队列上层系统订阅处理。这样做的好处是解耦平台不关心上层怎么用数据上层也不关心数据怎么来的。数据落库要设计好表结构。变量数据表至少要有设备 ID、变量 ID、变量值、采集时间戳事件表要有设备 ID、事件 ID、事件内容、发生时间告警表要有告警 ID、告警等级、触发时间、清除时间。时间戳统一用 UTC界面展示时再转本地时区避免时区混乱。对接 MES 时通常还要处理反向指令。MES 下发一个“开始加工”的指令平台转成 S2F41 发给设备设备执行后回 S2F42平台把结果回给 MES。这条链路要保证事务可追溯每条指令都要有唯一 ID方便出问题时回溯。5. 排查实录那些年踩过的坑5.1 连不上和断连问题连接问题分两类从来连不上和连上后频繁断。从来连不上先确认网络是否通用 telnet 测端口端口通但连不上检查是不是需要主动/被动模式匹配。HSMS 有主动和被动两种模式主动方发起 TCP 连接被动方监听。如果两边都配成主动或者都配成被动就永远连不上。这个配置错误我遇到过好几次尤其是不同厂商的设备默认模式不一样。连上后频繁断最常见的原因是 Linktest 没做或者做错了。设备长时间空闲会主动断开连接平台如果不做心跳设备误认为主机掉线就断开了。解决方法是按配置周期发 Linktest.req收到 Linktest.rsp 就认为链路正常。实操心得断线重连要做退避策略不要一断开就疯狂重连会把设备侧的连接数打满。我一般用 1 秒、2 秒、4 秒、8 秒这样递增最大间隔 30 秒重连成功后重置。5.2 消息解析类问题解析类问题里字节序和长度字段是两大高频错误。除此之外还有几个隐蔽的坑。一是ASCII 字符串的结束符有些设备会在字符串末尾补 0x00解析时要按实际长度截断不能依赖结束符。二是List 的嵌套层级有的设备把本该是 List 的数据项发成了单个值解析器要做容错。三是数据类型不匹配设备声明是 U4实际发的是 I4正负号处理不同数值会出现偏差。排查这类问题最好的办法是抓包。把原始字节流 dump 出来对照 SECS 消息格式手工解析一遍基本能定位到是哪一层出的问题。我一般会写一个消息转储工具把字节流按格式码逐层打印出来调试效率能提升一大截。5.3 性能与稳定性问题设备数量一多平台的性能瓶颈就会暴露。最常见的瓶颈是线程模型。如果每台设备配两个线程100 台设备就是 200 个线程线程切换开销会很大。我的做法是用 IO 多路复用epoll管理所有 socket用一个线程池处理消息解析和业务逻辑设备数量上百也能稳住。另一个瓶颈是消息处理速度。设备高频率上报事件时如果每条消息都同步落库数据库会跟不上。解决办法是异步落库加批量写入平台收到消息后先入内存队列后台线程按批次写入数据库。实测下来这种方式能把数据库写入压力降低一个数量级。还有内存泄漏的问题。事务表如果只加不删超时的事务一直堆积跑几天内存就满了。事务超时后一定要从 map 里移除并且释放关联的资源。这个坑我在第一个项目里踩过平台跑了三天内存报警排查发现是事务表里堆了几万个超时事务。5.4 常见问题速查表现象可能原因排查方向连接建立后立即断开主动/被动模式不匹配检查双方 HSMS 模式配置消息发出无应答W-bit 设置错误或设备未使能检查 W-bit确认设备状态应答匹配错乱System Bytes 分配或回绕问题检查事务分配逻辑数值解析错误字节序或格式码处理错误dump 原始字节手工解析报告未上报报告定义或链接顺序错误按 S2F33→S2F35→S2F37 顺序重做远程命令被拒设备未进 Online Remote 状态检查控制状态平台内存持续增长事务表未清理检查超时事务释放逻辑设备频繁断连未做 Linktest 心跳增加链路保活这张表基本覆盖了我这些年遇到的大部分问题。实际排查时我习惯先从链路层往上查先确认连接状态再看消息头和事务匹配最后看业务数据解析。按这个顺序走能少走很多弯路。最后再分享一个我自己总结的接入前检查清单每接一台新设备都过一遍确认网络和端口、确认 HSMS 模式、确认设备 ID、确认支持的 SxFy 列表、确认变量和事件 ID 定义、确认告警 ID 定义、确认配方格式。这七项确认完联调基本不会出大问题。设备厂商的协议文档往往写得很粗很多细节要靠在联调中一点点试出来所以留出足够的联调时间比什么都重要。我现在做项目排期SECS/GEM 对接环节至少预留两周前一周做链路和消息打通后一周做变量和事件的全量验证这样节奏比较稳。
返回列表