ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

secs4net实战:打通半导体设备与MES的SECS/GEM通信

secs4net实战:打通半导体设备与MES的SECS/GEM通信 1. 半导体工厂自动化里的那道“设备-MES通信”坎先说个真实场景。你在产线上盯着一台贴片机或者刻蚀机设备本地跑得挺好但工厂管理层要的“实时产量”“机台状态”“报警记录”“工艺参数追溯”全在设备里躺着传不出来。你问设备厂商要接口对方甩过来一份几百页的SECS/GEM文档——这就是半导体工厂自动化的经典开局设备是孤岛MES制造执行系统是大脑中间缺一条能稳定说话的“神经”。这份文档里的核心就是SECS协议族。SEMI组织为半导体、光伏、面板这些制造业定义了通信标准设备端叫SECS/GEM上位机那侧叫MES中间还有EAPEquipment Automation Program设备自动化程序这类中间件但本质上所有信息交互走的都是SECS这条链路。而secs4net就是.NET体系下实现这条链路的一套开源库。它帮你把协议栈里那些底层消息拆包、封包、序列化、超时重传的脏活累活都干了你在应用层只需要关注“这个设备消息代表什么业务含义”和“我要回复什么消息”。这篇我直接分享一套可用方案包含完整代码思路。我默认你对C#和基本异步编程有概念但不要求你之前碰过SECS。读完你至少能搭出一个能连上真实设备仿真器的程序理解S1F13/S1F14握手、S6F11事件上报这类最常见的交互流程知道消息发出去之后到底该怎么解析、怎么组织返回数据。目标不是让你背协议号而是让你知道“这条消息在整个体系里到底是怎么转起来的”。2. SECS/GEM协议栈和secs4net选型1.1 先搞懂SECS/GEM到底在传输什么很多人第一次看SECS文档都懵因为里面的缩写和编号太多了。其实可以把它拆成三层理解物理层SECS-I是老式的RS-232串口方案现在基本只有很老的设备才用主流是HSMS走TCP/IP设备作为Server监听端口上位机作为Client去连或者反过来取决于产线规划。消息层SECS-II定义了消息格式所有消息都由一个Stream和Function编号来标识比如S1F13表示“Establish Communication Request建立通信请求”S6F11表示“Event Report事件数据上报”。消息里的数据用Item项的嵌套结构存储类型有ASCII字符串、4字节无符号整数U4/U8、二进制等。应用层GEMGEM标准定义了设备必须具备的标准行为比如状态模型、报警管理、配方管理、事件上报、远程命令等。MES或EAP就是基于这层来和设备交互的。打个比方SECS-II是信封上的编号规范S1F13代表“我要建立联系”Item是信封里填的字段GEM是双方约定的“收到信之后该怎么回礼”的行为准则。secs4net做的事情就是让你不用手工去构造那种二进制流而是用C#的对象去表达“信封”和“字段”它负责序列化和收发。1.2 为什么选secs4net而不是从零开发我见过不少工厂项目一开始团队觉得“SECS不就是一个TCP协议嘛我们自己解析二进制就行”结果往往卡在几个隐蔽的坑上SECS消息头里的字节序、长度表示和分块规则Device ID、System Bytes、Block Number容易踩错。SECS-II的Item类型多List、ASCII、Binary、U4、U8、Boolean等嵌套层级复杂手工解析极易出边界问题。HSMS通信里有T3/T5/T6/T7/T8这么多定时器和重传状态机没有现成库的话这部分调试周期极长。所以我在实战里倾向于直接用成熟库。secs4net的优势是开源MIT协议没有商用授权包袱。底层用异步Socket实现自带消息编解码和心跳管理支持HSMS和SECS-I。提供SecsGem这个高层封装业务代码只需要注册消息处理器不用直接碰协议细节。.NET生态好和MES系统常见的技术栈容易集成踩坑时社区资料也能搜到。当然它也有一点学习成本主要在于消息回调模型和Item类型转换。下面我结合代码逐段讲。3. 环境准备与secs4net工程搭建2.1 创建项目并引入secs4net我用的是.NET 6以上环境Visual Studio 2022或者VS Code都行。创建一个控制台项目然后通过NuGet引入secs4netdotnet add package Secs4Net如果你的环境是.NET Framework 4.6.1也可以直接用NuGet包管理器搜索Secs4Net安装。这个库本身不依赖重型组件引入后在代码里能看到SecsGem、SecsMessage、Item这些核心类型说明装好了。提示secs4net有历史版本API略有区别。建议优先用新版本旧版命名空间里某些方法过时代码示例在新版下需要微调。写入项目后先建一个简单的连接测试确认库能正常加载Console.WriteLine(Secs4Net 加载成功开始创建 SecsGem 实例...);2.2 配置连接参数并启动HSMS链路HSMS连接有主动端Active和被动端Passive之分。通常工厂里设备是ServerMES/EAP是Client主动连接设备但设备仿真器上往往两种都支持。下面这段代码是让我们的程序作为Client去连接设备比如仿真器监听的5000端口using Secs4Net; var secsGem new SecsGem( ipAddress: 192.168.1.100, port: 5000, deviceId: 0, isActive: true); // 订阅连接状态变化方便观测链路 secsGem.ConnectionChanged (s, e) { Console.WriteLine($[连接状态] {e.State}); }; await secsGem.OpenAsync(); Console.WriteLine(HSMS 链路已打开等待设备会话建立...); await Task.Delay(Timeout.Infinite);这段代码里isActive: true表示我们主动去连设备端口。DeviceId 一般由工厂约定通常为0。启动后设备如果允许会立即进入Select状态双方可以互发消息。如果你只是想先验证协议层懒得找真设备可以用我常用的做法拿开源的SECS/GEM模拟器比如通过secs4net的Demo工程开一个被动端然后让我这个程序去连它。这样调试消息、验证解析逻辑特别方便。别一上来就盯着产线设备调环境不可控的时候问题根本分不清是网络还是协议。4. 核心业务消息握手、上报、报警、远程命令从这一节开始进入正题。我按实际项目里消息出现的频率来排序先S1F13/S1F14握手再S6F11事件上报然后S5F1报警最后说S2F41远程命令。每段我都会给出代码骨架和关键说明。3.1 S1F13/S1F14建立通信请求与应答设备上线后上位机第一件事往往是发一条S1F13Establish Communication Request询问设备的ID、软件版本、通信能力。设备要回S1F14Establish Communication Acknowledge把自己的MDLN型号、SOFTREV软件版本这些信息带回来。如果你用的是secs4net的SecsGem封装可以这样注册一个处理器专门响应上位机发来的S1F13secsGem.PrimaryMessageReceived async (sender, e) { var msg e.Message; Console.WriteLine($[收到消息] S{msg.S} F{msg.F}); if (msg.S 1 msg.F 13) { var reply new SecsMessage(1, 14, Reply to S1F13) { // S1F14 数据部分是一个 List依次包含 MDLN, SOFTREV Items Item.L( Item.A(ETCHER-3000), Item.A(V1.2.3)) }; e.Reply(reply); Console.WriteLine([回复消息] S1F14 已发送完成设备握手); } };如果你是需要主动向设备发起S1F13的场景比如你是EAP想确认设备在线可以用 secsGem.SendAsync 发送消息并等待回复。这里注意secs4net里消息类别分Primary和Secondary对方主动发来的、需要我们回复的就在PrimaryMessageReceived里处理我们自己发出去的通过SendAsync等待回执var request new SecsMessage(1, 13) { Items Item.L() // S1F13 的 SECS-II 内容一般是空或带少量信息 }; var reply await secsGem.SendAsync(request, TimeSpan.FromSeconds(10)); if (reply ! null reply.S 1 reply.F 14) { Console.WriteLine(握手成功设备型号: reply.Items[0].GetString()); } else { Console.WriteLine(S1F13 无响应检查链路和设备连接状态); }这里有个关键点SendAsync的返回值是对方的应答消息。但如果对方回复的是Secondary消息比如S1F14在对方是被动回复场景下其实是Primarysecs4net会统一处理成SendAsync的返回对象你只需要判断Stream和Function即可。注意设备一般不主动发S1F13除非你在上位机里把它设成了被动端。所以在收到S1F13时应尽快回复S1F14最好不要在事件处理器里做重业务逻辑否则容易触发T3超时。T3规定的是“Primary Message发出后等待Secondary Reply的时间”一般设备配置为5到45秒超出就断链警告。3.2 S6F11/S6F19把事件数据和SPC数据上报给MESS6F11是全厂最常用的消息之一代表“Event Report”。设备上料完成、加工完成、清洁完成、发生模式切换这些事都可以通过S6F11告诉上位机。先看一个最简单的例子假设我们要上报“设备当前处于Processing状态”的事件事件ID为999附带一个DataID和一个状态字符串var eventMessage new SecsMessage(6, 11) { Items Item.L( Item.U4(1001), // DataID通常是上下文编号 Item.U4(999), // EventID事件ID要和MES侧配置对齐 Item.L( Item.L( Item.U4(1), // ReportID Item.L( Item.A(PROCESSING) // 状态值 ) ) ) ) }; await secsGem.SendAsync(eventMessage, TimeSpan.FromSeconds(10)); Console.WriteLine(S6F11 事件已上报);这里结构看起来复杂但拆开就清楚了S6F11的数据部分是个嵌套的List最外层包含“Data ID、Event ID、Report Data集合”而Report Data的每一个Report都是一个List里面是RPTID报告编号一组变量VID 值。你如果直接看过SECS-II报文会发现它就是这样一个层层嵌套的Item结构secs4net的Item.L()就是用来构造这种List的。事件上报之后上位机通常会回一条S6F12Event Report Acknowledge。所以你在做EAP或MES接入时要检查SendAsync的返回是否S6F12且ACKC6为00表示成功。这个ACKC6是S6F12数据里的第一个字节/单词一般取0表示接受其他值表示拒绝理由。S6F19则是“Individual Report Request”通常用于周期性的数据采集比如配方参数、工艺数据、SPC数据。设备收到S6F19后回复S6F20把我们要求的Report Pack数据发回来。这部分实现和S6F11类似区别在于是MES主动拉数据// 假设我们要向设备请求一个名为“关键工艺参数”的报表 var request new SecsMessage(6, 19) { Items Item.L( Item.U4(2001), // DataID Item.L( Item.U4(5) // ReportID5表示参数报表编号 ) ) }; var reply await secsGem.SendAsync(request, TimeSpan.FromSeconds(10)); if (reply ! null reply.S 6 reply.F 20) { Console.WriteLine(收到S6F20报表数据: reply.ToXml()); }这里我用了 reply.ToXml() 来快速打印消息结构secs4net提供了把消息转成XML/JSON的调试接口这在联调时特别方便能看到每个Item的类型和值。我强烈建议你在联调阶段把收发消息全量打到日志里格式用XML或JSON不然嵌套数据结构根本没法肉眼看。3.3 报警上报S5F1出问题要第一时间通知MES设备报警是工厂里最不能丢的消息。S5F1Alarm Report里包含ALCD报警级别和类型和ALID报警ID、ALTX报警文本。MES或者EAP收到后一般回S5F2。secs4net实现报警上报非常简单var alarmMsg new SecsMessage(5, 1) { Items Item.L( Item.U4(2), // ALCD这里2表示“错误报警需要人工介入” Item.U4(3021), // ALID报警编号要和设备手册对应 Item.A(Coolant temp too high) // ALTX报警描述 ) }; await secsGem.SendAsync(alarmMsg, TimeSpan.FromSeconds(10));S5F1发送后上位机回S5F2里面带ACKC50表示确认收到。注意有些低端设备在上电时会一次性上报一堆历史报警EAP要有去重和状态关联能力别把5分钟前的报警当成当前报警处理。报警的清除通常用ALCD值来区分比如ALCD为1是清除报警为2是报警发生别搞反。3.4 S2F41远程命令让MES能“遥控”设备如果说事件上报是设备向MES汇报那远程命令就是MES向设备下发指令。S2F41可以配置参数、启动流程、停止流程等具体语义靠CPNAME命令名和CPVAL命令值区分。比如我们要下发“启动配方”命令设备端响应一个S2F42告诉我们命令是否执行成功var cmdMsg new SecsMessage(2, 41) { Items Item.L( Item.A(START_RECIPE), // CPNAME Item.L( Item.L( Item.A(RECIPE_ID), Item.A(RECIPE_A01) ) ) ) }; var reply await secsGem.SendAsync(cmdMsg, TimeSpan.FromSeconds(20)); if (reply ! null reply.S 2 reply.F 42) { // ACKC2: 0 表示完成其他值表示错误码 Console.WriteLine(远程命令返回ACKC2 reply.Items[0].GetU4()); } else { Console.WriteLine(S2F41 命令超时或未收到正确的S2F42); }由此可见远程命令的流程往往比事件上报更复杂因为涉及多次握手或额外的数据确认。实际工厂里MES下发Recipe后通常还要通过S6F11的事件上报来确认设备真正执行了。所以“命令下发→设备确认→执行中→执行结束事件上报”这种消息链是EAP开发里最常见的模式。5. 与MES系统对接的常见模式和实战细节4.1 直连模式与EAP中间层模式secs4net本身能跑通SECS链路但MES系统对接时往往有两条路线第一种是设备直接连MES。适合小型工厂、老旧产线MES侧实现了SECS Server模块设备接入后直接上报。优点是链路短、延迟低缺点是MES的核心职责是生产管理把设备通信塞进去容易让系统变得臃肿协议升级或设备类型增加时MES侧改动大。第二种是通过EAP中间层。EAP负责跟不同厂商的设备打交道把SECS消息翻译成标准化的数据模型再通过API/REST/消息队列等接口把数据同步给MES。secs4net非常适合写这种EAP采集服务。我推荐用工厂里较新、规模较大的项目采用这种方式因为设备差异被EAP屏蔽了MES只需要面对一套标准接口。如果你负责设计可以考虑这样的分层采集层用secs4net实例管理每一台设备的连接。会话层把SecsMessage转换成业务对象比如EquipmentEvent、AlarmInfo、RecipeResult。集成层用内存队列或消息队列把数据推给上游MES。4.2 数据映射与消息时序实战不论哪种模式都得解决数据映射问题。设备上报的是EventID、ReportID、VIDMES里对应的是“工单号”“设备状态”“工艺参数”。这块必须靠配置文件或数据库做一个映射表别写死在代码里。我自己常用的数据映射方式是定义一个EquipmentEvent类包含时间戳、设备ID、事件名、参数字典然后在secs4net的消息处理回调里把SecsMessage转换成这个对象推给统一的消息总线。这样即使将来换设备型号或者MES侧字段变了只改映射逻辑不用动通信层。再说一个时序细节SECS通信里同一时刻同一套System Bytes只能有一个Pending的Primary消息在等待回复。如果你并发调用多个SendAsyncsecs4net在内部已经做了管道化处理系统字节会自动分配。但如果你自己用底层Socket实现就非常容易把消息搞串。这也是选secs4net帮我们省事的一点。实操建议无论你搞直连还是EAP消息日志必须保留至少30天。产线出问题时没有日志根本没法回放“当时设备到底有没有上报过这个事件”。我排过太多生产事故最后都是靠日志定位到设备端压根没发消息或者发了但被防火墙拦了。4.3 断线重连与状态同步HSMS链路不是永久的。设备重启、网络调整、上位机重启都会导致断链。设备端一般会主动检测TCP状态如果链路断开它就会回到Not Connected状态。此时EAP必须自动重连并且在重连后重新做一次状态同步。secs4net的OpenAsync是一次性打开连接。真实项目里我会在外层写一个连接管理器用定时器或循环去检查连接状态一旦发现断开就延迟重试while (true) { if (secsGem.State ! ConnectionState.Selected) { Console.WriteLine(链路断开尝试重连...); await secsGem.OpenAsync(); } await Task.Delay(5000); }重连成功后别忘了重新执行一次S1F13握手并且向MES发送一次“上线事件”或状态轮询让MES侧恢复对设备的实时跟踪。如果设备支持可以再主动获取一下完整状态报表避免错过断链期间发生的事件。6. 常见问题与排查技巧实录5.1 链路层问题连不上、秒断、反复Select这是最常遇到的。现象是程序启动后连不上设备端口或者刚连上就断开。优先排查顺序确认设备的HSMS端口和IP。很多设备默认监听5000或5001但工厂网络做过隔离上位机网段和设备网段不通这就需要在交换机和防火墙上放行。用telnet或nc测一下端口telnet 设备IP 5000能通说明TCP层没问题。看设备的连接模式。有的设备只允许一个Client连接。如果之前有人开了别的连接你这边会一直被拒绝或秒断。检查DeviceID。有些设备对DeviceID有校验配置不匹配会拒绝通信或响应异常。如果你能收到S1F13但回S1F14后对方就断链那多半是S1F14里的数据格式对方解析不了。我遇到过一台老设备要求MDLN必须是固定8位ASCII少了空格它直接报错。这种只能对着设备手册调。5.2 T3超时与“秒回”原则T3超时太常见了。现象是我们发了一条S1F13或S6F11之后进度条转了几秒secs4net报Timeout设备看起来也没反应。原因是对方在T3时间内没返回应答。注意这里“应答”不是指业务上的最终结果而是协议层面的“收到了”。比如S6F11发出后设备要在T3时间内先回一个S6F12哪怕内容是“待处理”你后续才能等到真正的处理结果。很多新人在消息处理器里做数据库写入、文件解析、远程调用耗了几秒导致T3超时链路就被判死了。最佳实践消息处理器里只做“最小必要处理”。收到消息立刻返回确认把真正的业务解析放到后台任务或消息队列里。这一条能帮你避开90%的“偶发断联”。如果你是在secs4net的PrimaryMessageReceived回调里做耗时操作一定要小心。这个回调跑在通信线程上阻塞它会影响后续消息的收发。我的做法是回调里先e.Reply()返回一个临时确认然后把SecsMessage的Item拷贝到后台Channel里让独立Worker慢慢处理。5.3 SECS-II数据格式解析踩坑拿S6F11举例很多新手直接写 msg.Items[0] 取数据然后发现取出来的是Item对象不是string也不是int。你需要用GetString()、GetU4()这些方法而且要检查Item的类型是否匹配。比如设备可能把VID发成了ASCII的123而不是U4的123你按U4解析就会异常或得到垃圾值。secs4net的Item类型和转换方法需要花点时间熟悉。我喜欢在解析逻辑里加一层TryConvertToInt32、TryConvertToString对设备数据进行兜底转换。工厂设备厂商水平参差不齐有些SECS实现本身就违反规范与其跟厂商吵不如在自己这边兼容一顿。5.4 日志里看不到消息先确认消息过滤secs4net支持日志记录。如果你发现日志很安静先确认LogLevel设成了Information或Debug别默认关掉了消息级日志。日志输出了XML格式的完整消息体这样排查数据映射问题就会快很多。我在项目里还习惯把System Bytes也打出来。这个字段用来关联Primary和Secondary消息。排时序问题的时候如果发现某个回复找不到请求看System Bytes就能对上号。7. 完整代码整合与运行建议前面分模块讲了消息处理这里我整合一个简单的可运行版方便你直接跑起来改using Secs4Net; class Program { static async Task Main(string[] args) { var secsGem new SecsGem( ipAddress: 127.0.0.1, port: 5000, deviceId: 0, isActive: true); secsGem.ConnectionChanged (s, e) Console.WriteLine($[连接状态] {e.State}); secsGem.PrimaryMessageReceived async (s, e) { var msg e.Message; Console.WriteLine($[收到] S{msg.S}F{msg.F}); Console.WriteLine(msg.ToXml()); if (msg.S 1 msg.F 13) { e.Reply(new SecsMessage(1, 14) { Items Item.L( Item.A(SIMULATOR-1000), Item.A(V1.0.0)) }); } }; await secsGem.OpenAsync(); Console.WriteLine(Press any key to send test event...); Console.ReadKey(); var evt new SecsMessage(6, 11) { Items Item.L( Item.U4(1), Item.U4(100), Item.L( Item.L( Item.U4(1), Item.L(Item.A(RUNNING))))) }; var reply await secsGem.SendAsync(evt, TimeSpan.FromSeconds(10)); Console.WriteLine($S6F11 回复: {(reply null ? 无响应 : $S{reply.S}F{reply.F})}); await secsGem.CloseAsync(); } }这段代码放到前面工程里就能跑。测试时最好先用一个模拟器当成对端来验证然后在产线设备上做小流量验证。记住直接拿真机联调第一次大概率会踩协议细节问题。8. 最后分享一个实战心得我自己刚开始写SECS通信的时候最崩溃的其实是“设备厂商连协议都不按标准来”。有的设备S1F13里的MDLN比标准短有的S6F11里ReportID放错层级还有的把报警上报写成了S5F3而不是S5F1。遇到这种问题光翻协议文档没用必须用日志把报文一条条拉出来对然后再跟厂商工具有限的人确认“你们这个字段到底按什么语义发的”。所以我才强调选secs4net不只是选一个协议栈更是让你有时间把精力花在业务语义和异常兼容上。工厂项目里稳定大于功能兼容大于优雅。另外一个小提醒代码里所有设备连接参数包括IP、端口、DeviceId、超时时间尽量抽到配置文件里别硬编码。产线设备多了之后每台设备参数都不一样领导随时会让你快速加一台新设备配置化能救你命。这算是我在实际项目中踩过几次坑之后的教训。
返回列表