ARTICLE DETAIL

资讯详情

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

SECS/GEM 对接效率翻倍:sec4j-master 源码实战与模拟器搭建

SECS/GEM 对接效率翻倍:sec4j-master 源码实战与模拟器搭建 简介secs4j-master 是一套面向半导体设备自动化领域的 Java 版 SECS/GEM 协议实现适合从事设备通信、工厂自动化系统开发的工程师与学习者使用。它把 SECS-I、SECS-II 的物理层与应用层协议以及 GEM 规范中的设备初始化、状态报告、命令控制、数据采集等交互流程封装成可直接调用的类与方法帮助开发者免去从零编写底层通信逻辑的繁琐。资源包共 80 个文件以 72 个 java 源码为主体辅以 4 个 txt 说明、3 个 xml 配置和 1 个 md 文档整体约 109KB结构紧凑、便于阅读与二次开发。内容涵盖 SECS 连接管理、消息构建与解析、GEM 接口、事件订阅发布、数据交换模型、异常处理及单元与集成测试用例可帮助读者快速搭建设备与主机系统之间的通信桥梁理解协议标准并提升自动化控制系统的开发效率。目前已有 2500 人学习下载。1. secs4j-master 这套 SECS/GEM 源码到底能帮你省下多少对接时间如果你在半导体封测厂做过设备对接大概率经历过这种场面设备厂商的工程师站在机台旁边手里拿着厚厚一本 SECS/GEM 接口文档你在这边对着 EAP 系统一行行调报文一个 S1F13 的握手报文来回抓包抓了一下午。SECS/GEM 这套协议本身不复杂但它的报文结构、状态机流转、超时重传机制在没有一套趁手代码库的情况下能把人磨到怀疑人生。secs4j-master 就是在这种场景下值得认真看一遍的 Java 开源实现它把 SECS-II 消息编解码、HSMS 连接管理、GEM 状态模型这些底层脏活累活都封好了你拿到手就能直接搭一个能跑通的设备侧或主机侧通信端。这篇文章不聊虚的就讲这套源码的结构怎么读、环境怎么搭、最小通信链路怎么跑起来、参数怎么调、以及现场最容易翻车的几个地方。适合正在做 EAP 系统实施、设备 SECS/GEM 协议对接、或者想自己写一个 SECS/GEM 模拟器来测机的工程师。2. 拆开 secs4j-master从包结构到核心对象模型2.1 先搞清楚这套源码的模块划分拿到 secs4j-master 之后不要急着找 main 方法跑起来。先花十分钟把目录结构过一遍后面能省你几个小时。常见的 secs4j 实现通常按职责切成几块消息层负责 SECS-II 的 Item 树构建和编解码通信层负责 HSMS 的 TCP 连接、Select/Deselect 状态机、心跳和超时会话层负责把消息和连接绑定起来管理GEM 层则是在 SECS-II 之上定义设备状态模型、数据采集、报警、事件报告这些标准行为。你打开源码目录一般能看到类似这样的包结构secs4j/ ├── secs/ │ ├── SecsMessage.java // SECS-II 消息顶层抽象 │ ├── Secs2.java // Item 接口定义 │ ├── Item.java // 基础 Item 实现 │ └── ... ├── hsms/ │ ├── HsmsConnection.java // HSMS 连接管理 │ ├── HsmsMessage.java // HSMS 报文封装 │ ├── HsmsDecoder.java // 报文解码 │ └── ... ├── gem/ │ ├── GemEquipment.java // 设备侧 GEM 模型 │ ├── GemHost.java // 主机侧 GEM 模型 │ └── ... └── session/ ├── SecsSession.java // 会话管理 └── ...这个划分方式很直白你要发一条 S1F1走的是 SecsMessage 构建 Item 树然后交给 HsmsConnection 发出去对面回 S1F2HsmsDecoder 解出字节流SecsSession 根据 SessionID 找到对应的会话回调。理解这条链路之后你改代码或者排查问题就有了方向——报文发不出去看 hsms 包报文内容不对看 secs 包状态机不对看 session 和 gem 包。2.2 核心对象SecsMessage、Item 和 HsmsConnectionSecsMessage 是整个库最核心的对象。一条 SECS-II 消息由 Stream、Function、W-Bit等待回复标志和消息体组成。消息体是一棵 Item 树Item 可以是 List、ASCII、Binary、Boolean、U1/U2/U4/U8、I1/I2/I4/I8、F4/F8 这些类型。你在代码里构建一条 S1F13 大概是这个写法// 构建 S1F13 消息W-Bit 置 true 表示需要回复 SecsMessage s1f13 new SecsMessage(1, 13, true); // 消息体是一个 List里面放两个 ASCII Item Item body Item.L( Item.A(EQUIPMENT_ID), Item.A(1.0.0) ); s1f13.setItem(body);这段代码的逻辑很清晰new SecsMessage(1, 13, true)创建一条 Stream 1 Function 13 且需要回复的消息Item.L(...)创建一个 List 类型的 Item里面嵌套两个 ASCII 字符串。参数说明上Stream 和 Function 是必填的W-Bit 决定了发送后是否等待对方回复如果置 false 则发完即止不会触发超时重传。HsmsConnection 则是管 TCP 连接的。它负责建立连接、发送 Select 请求、维护连接状态、处理心跳。你初始化一个连接通常需要指定 IP、端口、设备角色Active 还是 Passive、以及 SessionID。Active 端主动发起 TCP 连接Passive 端监听端口等连接进来。这个角色分配在现场对接时经常搞反后面避坑章节会细说。2.3 消息编解码从字节流到 Item 树SECS-II 的编码格式是自描述的每个 Item 前面有格式字节和长度字节。格式字节的高 6 位表示 Item 类型低 2 位表示长度字节数长度字节表示后续数据的字节数。List 类型比较特殊它的长度表示的是子 Item 的个数而不是字节数。这个细节在手动解析报文时特别容易搞错。源码里的编解码逻辑一般集中在 Secs2 相关的工具类里。编码时库会递归遍历 Item 树按类型写入格式字节和长度再写入实际数据。解码时反过来先读格式字节判断类型和长度字节数再读长度再根据类型读取对应字节数的数据。对于 List读到的长度是子 Item 数量然后递归解码每个子 Item。你不需要自己手写编解码但理解这个机制对排查问题很关键。比如你发现收到的报文解析出来是乱码或者 Item 类型不对大概率是对面发的报文格式和你的解码器预期不一致这时候抓原始字节流对照 SECS-II 规范看格式字节就能定位。3. 把 secs4j-master 跑起来环境、依赖和最小通信链路3.1 环境准备和依赖引入secs4j 是 Java 项目你需要 JDK 8 或以上。构建工具看源码里带的是 Maven 还是 Gradle一般开源项目两种都会提供。如果你是把源码集成到自己的 EAP 项目里最省事的方式是先把源码编译成 jar 包然后作为本地依赖引入。用 Maven 构建的话在项目根目录执行# 编译并安装到本地 Maven 仓库 mvn clean install -DskipTests执行完之后本地仓库里就会有 secs4j 的 jar 包。然后在你的 EAP 项目 pom.xml 里加依赖dependency groupIdcom.example.secs4j/groupId artifactIdsecs4j-core/artifactId version1.0-SNAPSHOT/version /dependencygroupId 和 version 要根据源码里 pom.xml 的实际定义来填不要照抄这里的示例。如果你不想装到本地仓库也可以直接把源码目录作为模块引入你的项目在 pom 里加module指向 secs4j 的目录。依赖方面secs4j 一般只依赖 Netty 或者 Java 原生 NIO 做网络通信日志用 SLF4J。如果你看到源码里有 Netty 的依赖说明它用 Netty 做 HSMS 的 TCP 层性能和连接管理会更稳一些。没有 Netty 的话就是原生 Socket功能一样但高并发场景下需要你自己多关注线程模型。3.2 搭一个最小可跑的 Passive 端先跑 Passive 端因为设备侧通常是 Passive 角色等主机来连。下面是一个最小化的 Passive 端启动代码// 创建 HSMS 连接配置 HsmsConnectionConfig config new HsmsConnectionConfig(); config.setPort(5000); // 监听端口 config.setRole(HsmsRole.PASSIVE); // 被动模式等对方来连 config.setSessionId(0); // SessionID主动方和被动方要一致 config.setDeviceId(0); // 设备 ID // 创建连接实例 HsmsConnection connection new HsmsConnection(config); // 注册消息回调处理收到的消息 connection.setMessageHandler((session, message) - { System.out.println(收到消息: S message.getStream() F message.getFunction()); // 如果是 S1F13回复 S1F14 if (message.getStream() 1 message.getFunction() 13) { SecsMessage reply new SecsMessage(1, 14, false); Item body Item.L( Item.B(0), // 通信确认码0 表示成功 Item.L( Item.A(EQUIPMENT_ID), Item.A(1.0.0) ) ); reply.setItem(body); session.send(reply); } }); // 启动连接 connection.start(); System.out.println(Passive 端已启动监听端口 5000);这段代码的逻辑是配置 Passive 角色和监听端口创建连接对象注册消息处理器启动。消息处理器里判断收到的是 S1F13 就回一条 S1F14通信确认码填 0 表示成功。参数上SessionID 和 DeviceID 在单设备场景下填 0 就行多设备场景下需要区分。端口选 5000 是常见做法但现场如果和其他服务冲突改成 5001 或别的也行只要两端一致。3.3 搭一个 Active 端并发起通信Active 端通常是 EAP 主机侧主动去连设备。代码结构和 Passive 端类似区别在角色和连接目标HsmsConnectionConfig config new HsmsConnectionConfig(); config.setHost(192.168.1.100); // 设备 IP config.setPort(5000); // 设备端口 config.setRole(HsmsRole.ACTIVE); // 主动模式 config.setSessionId(0); config.setDeviceId(0); HsmsConnection connection new HsmsConnection(config); connection.setMessageHandler((session, message) - { System.out.println(收到回复: S message.getStream() F message.getFunction()); }); connection.start(); // 等连接建立后发一条 S1F13 SecsMessage s1f13 new SecsMessage(1, 13, true); Item body Item.L( Item.A(HOST), Item.A(1.0.0) ); s1f13.setItem(body); // 发送并等待回复超时时间 5000ms SecsMessage reply connection.sendAndWait(s1f13, 5000); if (reply ! null) { System.out.println(收到 S1F14通信建立成功); } else { System.out.println(超时未收到回复检查连接和对方状态); }这里sendAndWait是同步等待回复的方法超时时间设 5000ms。现场如果网络延迟大或者设备处理慢可以适当调大到 10000ms。但也不要设太大否则出问题时你等半天才报错排查效率低。一般 5 到 10 秒是合理范围。3.4 验证通信是否真正建立两端都跑起来之后怎么确认通信真的通了最直接的方法是看日志。Active 端发出 S1F13 后Passive 端应该打印「收到消息: S1F13」然后 Active 端打印「收到回复: S1F14」。如果 Active 端超时没收到回复先检查 Passive 端有没有收到消息。Passive 端收到了但 Active 端没收到回复说明 Passive 端的回复没发出去或者发错了地址。另一个验证手段是抓包。在 Active 端所在机器上用 tcpdump 抓 5000 端口的包# 抓取 5000 端口的 TCP 报文输出到文件 tcpdump -i eth0 port 5000 -w secs_capture.pcap然后用 Wireshark 打开 pcap 文件看 TCP 流。HSMS 的 Select 请求和 S1F13 报文都能在 TCP 载荷里看到。如果你看到 TCP 三次握手成功但后面没有数据说明 HSMS 的 Select 阶段没过如果 Select 过了但 S1F13 没发出去检查代码里 sendAndWait 是不是被异常中断了。4. 参数调优和现场适配超时、重传、SessionID 怎么设4.1 超时参数T3、T5、T6、T7、T8SECS/GEM 标准里定义了几个关键超时参数secs4j 里一般都能配。T3 是回复超时发一条 W-Bit 为 true 的消息后等多久算超时默认 45 秒现场通常调到 10 到 30 秒。T5 是连接分离超时TCP 连上后多久没收到 Select 请求就断开默认 10 秒。T6 是 Select 超时发 Select 请求后等多久没收到 Select 响应就重试。T7 是 Not Select 超时连接处于 Not Select 状态多久没动静就断开。T8 是网络字符间超时两个字节之间超过这个时间算一帧结束默认 5 秒。这些参数在 HsmsConnectionConfig 里一般都有对应的 setter。现场调优的原则是T3 不要设太小设备处理慢的时候容易误超时T5 和 T7 不要设太大否则连接断了你半天发现不了。我一般会把 T3 设 15 秒T5 设 10 秒T6 设 5 秒T7 设 10 秒T8 保持默认。4.2 重传机制什么时候重发重发几次HSMS 的重传分两种一种是 TCP 层的重传这个操作系统管你不用操心另一种是 SECS 层的重传比如你发了 S1F13 没收到 S1F14要不要重发。secs4j 里通常不自动重传 SECS 消息需要你在业务层自己控制。常见做法是发 W-Bit 消息时设一个重试次数比如 3 次每次间隔 T3 超时时间。3 次都没回复就报通信异常触发告警。重传要注意一点如果对方其实收到了消息也处理了只是回复在路上丢了你重传会导致对方重复处理。所以重传的消息要带事务 ID 或者序列号让对方能去重。SECS-II 本身没有强制的事务 ID 字段但你可以自己在消息体里加一个序列号 Item双方约定好去重逻辑。4.3 SessionID 和 DeviceID 的分配SessionID 在 HSMS 层用来区分不同的连接。单设备对接时填 0 就行多设备场景下每个设备一个 SessionID从 0 开始递增。DeviceID 在 SECS-II 层用来区分设备单设备填 0多设备填设备编号。这两个 ID 在 Active 端和 Passive 端必须一致否则消息会被丢弃或者路由到错误的会话。现场最容易出的问题是设备厂商说他们的 SessionID 是 1你这边填了 0结果 Select 请求发过去对方不认连接建不起来。所以对接前一定要和设备厂商确认这两个 ID 的值写进接口文档里不要靠猜。5. 避坑指南SECS/GEM 对接现场最容易翻车的五个地方5.1 现象Select 请求发出去了但对方一直不回 Select 响应原因最常见的是 SessionID 不匹配。HSMS 的 Select 请求里带 SessionID对方收到后检查这个 ID 是不是自己期望的不匹配就直接丢弃不会回响应。另一个可能是对方处于 Not Select 状态但没准备好接收 Select比如设备还没初始化完。解决先确认两端的 SessionID 配置一致。如果一致抓包看 Select 请求有没有到达对方。到达了但没响应联系设备厂商确认他们的 HSMS 状态机是不是正常。如果对方是 Passive 端且刚启动等几秒再试有些设备初始化需要时间。5.2 现象S1F13 发出去了收到 S1F14 但通信确认码不是 0原因S1F14 的消息体里第一个 Item 是通信确认码0 表示成功非 0 表示失败。非 0 的常见原因是对面认为你的设备 ID 不对、或者协议版本不匹配、或者对方当前状态不允许建立通信。解决看确认码的具体值对照 SECS/GEM 标准里的定义排查。如果是设备 ID 问题检查 DeviceID 配置如果是版本问题检查 S1F13 里带的版本号是不是对方支持的。有些设备厂商会在文档里写明他们期望的版本号格式按文档填。5.3 现象通信建立后发 S1F1 没收到 S1F2原因S1F1 是 Are You There 消息用来确认对方还在线。没收到 S1F2 可能是对方处理超时了或者对方的 S1F1 处理逻辑有 bug或者消息在传输过程中被防火墙拦了。解决先抓包确认 S1F1 有没有到达对方。到达了但没回复联系设备厂商查他们的 S1F1 处理逻辑。没到达的话检查网络和防火墙SECS/GEM 用的端口一般是 5000 或 5001确认这些端口没有被安全策略拦截。5.4 现象消息体里的中文或特殊字符解析出来是乱码原因SECS-II 的 ASCII Item 理论上只支持 ASCII 字符集中文和特殊字符不在标准范围内。如果你往 ASCII Item 里塞了中文编码解码时就会出问题。有些实现用 UTF-8 编码 ASCII Item但对方可能用 ISO-8859-1 解码两边不一致就乱码。解决不要在 ASCII Item 里放中文。如果确实需要传中文用 Binary Item 自己编码双方约定好编码格式。或者用多个 ASCII Item 拼接但这样对方解析起来也麻烦。最稳妥的做法是遵循标准ASCII Item 只放 ASCII 字符。5.5 现象连接跑了一段时间后自动断开日志显示 T7 超时原因T7 是 Not Select 超时连接处于 Not Select 状态超过 T7 时间没有收到任何报文就会断开。这通常是因为双方都没有发心跳或者保活消息。SECS/GEM 没有强制的心跳机制但很多实现会用 S1F1 或者自定义的保活消息来维持连接。解决在应用层加一个定时保活任务每隔一段时间比如 T7 的一半发一条 S1F1 或者自定义保活消息。如果对方不支持 S1F1可以发一条 W-Bit 为 false 的 S1F1不需要回复只是为了刷新 T7 计时器。或者把 T7 设大一点但这不是根本解决办法保活还是要做。6. 进阶技巧用 secs4j-master 搭一个可配置的 SECS/GEM 模拟器6.1 为什么需要模拟器现场对接时设备不一定随时可用。设备在跑生产的时候你没法随便发消息测试设备厂商的工程师也不一定随时在场。这时候一个可配置的 SECS/GEM 模拟器就很有价值。你可以用它来模拟设备侧的行为测试 EAP 系统的消息处理逻辑也可以模拟主机侧测试设备端的响应。secs4j-master 的代码结构很适合改造成模拟器因为它的消息层和通信层是解耦的你只需要在消息处理器里加配置化的响应逻辑就行。6.2 用配置文件定义消息响应规则模拟器的核心思路是把「收到什么消息、回复什么消息」做成配置而不是硬编码在代码里。下面是一个简单的 JSON 配置示例{ rules: [ { stream: 1, function: 13, replyStream: 1, replyFunction: 14, replyBody: [ {type: B, value: 0}, {type: L, value: [ {type: A, value: SIMULATOR}, {type: A, value: 1.0.0} ]} ] }, { stream: 1, function: 1, replyStream: 1, replyFunction: 2, replyBody: [ {type: L, value: [ {type: A, value: SIMULATOR}, {type: A, value: 1.0.0} ]} ] } ] }然后在消息处理器里加载这个配置根据收到的 Stream 和 Function 查找匹配的规则动态构建回复消息// 加载配置 ListRule rules loadRules(simulator_rules.json); connection.setMessageHandler((session, message) - { for (Rule rule : rules) { if (rule.getStream() message.getStream() rule.getFunction() message.getFunction()) { SecsMessage reply new SecsMessage( rule.getReplyStream(), rule.getReplyFunction(), false ); reply.setItem(buildItem(rule.getReplyBody())); session.send(reply); return; } } // 没有匹配规则不回复 System.out.println(未匹配到规则: S message.getStream() F message.getFunction()); });这段代码的逻辑是遍历规则列表找到匹配的规则就构建回复消息并发送没找到就不回复。buildItem方法根据配置里的类型和值递归构建 Item 树。参数上replyBody里的 type 对应 SECS-II 的 Item 类型value 是实际值。List 类型的 value 是一个嵌套数组递归构建。6.3 模拟器配置的扩展思路基础版模拟器只能做静态响应实际测试中你可能需要更复杂的行为。比如收到 S1F1 后延迟几秒再回复模拟设备处理慢的场景或者收到 S2F33 后根据当前状态决定回复 S2F34 还是 S2F35。这些可以在规则里加 delay 字段和条件字段来实现。另一个扩展方向是支持变量。比如 S1F13 的回复里设备 ID 不是固定的而是从配置里读一个变量。这样你可以通过改变量来模拟不同设备不用改规则文件。实现上就是在 buildItem 的时候把变量占位符替换成实际值。6.4 用模拟器做回归测试模拟器搭好之后你可以把它集成到 CI 流程里做回归测试。每次 EAP 代码有改动自动启动模拟器跑一遍预设的消息序列检查 EAP 的响应是否符合预期。这样能在代码合并前就发现协议层的回归问题不用等到现场对接时才发现。我自己的习惯是每对接一个新设备先把设备厂商的接口文档里的消息列表整理成模拟器规则然后在本地用模拟器把 EAP 的逻辑跑通再去现场连真设备。这样现场调试的时间能压缩一半以上因为协议层的问题在本地就暴露完了现场只需要处理设备特有的行为差异。希望帮到你。本文还有配套的精品资源点击获取
返回列表